From fb7c368bffb2f4f0d528cf69a994ebdccf77c910 Mon Sep 17 00:00:00 2001 From: barhen Date: Thu, 18 May 2023 19:53:31 -0500 Subject: [PATCH 1/2] Backend Login Feature initial commit --- ...tsContext.cs => BiskilogClientsContext.cs} | 10 +-- Server/Program.cs | 81 ++++++++++++++++++- Server/appsettings.json | 12 ++- Shared/Biskilog Accounting.Shared.csproj | 1 - Shared/Interfaces/IAuthService.cs | 25 ++++++ Shared/Interfaces/ITokenService.cs | 15 ++++ 6 files changed, 133 insertions(+), 11 deletions(-) rename Server/{DevBiskilogclientsContext.cs => BiskilogClientsContext.cs} (91%) create mode 100644 Shared/Interfaces/IAuthService.cs create mode 100644 Shared/Interfaces/ITokenService.cs diff --git a/Server/DevBiskilogclientsContext.cs b/Server/BiskilogClientsContext.cs similarity index 91% rename from Server/DevBiskilogclientsContext.cs rename to Server/BiskilogClientsContext.cs index 87824c9..d009d98 100644 --- a/Server/DevBiskilogclientsContext.cs +++ b/Server/BiskilogClientsContext.cs @@ -5,13 +5,13 @@ using Microsoft.EntityFrameworkCore; namespace Biskilog_Accounting.Server; -public partial class DevBiskilogclientsContext : DbContext +public partial class BiskilogClientsContext : DbContext { - public DevBiskilogclientsContext() + public BiskilogClientsContext() { } - public DevBiskilogclientsContext(DbContextOptions options) + public BiskilogClientsContext(DbContextOptions options) : base(options) { } @@ -30,10 +30,6 @@ public partial class DevBiskilogclientsContext : DbContext public virtual DbSet Userauths { 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_biskilogclients;user=biskilog;password=mefbuk-6niFsu-fytrew", ServerVersion.Parse("10.3.38-mariadb")); - protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder diff --git a/Server/Program.cs b/Server/Program.cs index 3af2c77..8a53ee1 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,7 +1,74 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text.Json.Serialization; +using System.Text; +using Biskilog_Accounting.Server; var builder = WebApplication.CreateBuilder(args); +// Add services to the container. +builder.Services.AddControllers().AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles); +builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); +builder.Logging.ClearProviders(); +builder.Logging.AddConsole(); +builder.Services.AddEntityFrameworkMySql().AddDbContext(options => +{ + options.UseMySql(builder.Configuration.GetConnectionString("Connection"), new MariaDbServerVersion(new Version())); +}); +//builder.Services.AddScoped(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + ); +}); +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => +{ + options.RequireHttpsMetadata = false; + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuer = true, + ValidateAudience = true, + ValidAudience = builder.Configuration["Jwt:Audience"], + ValidIssuer = builder.Configuration["Jwt:Issuer"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) + }; +}); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyBlazor", Version = "v1" }); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type=ReferenceType.SecurityScheme, + Id="Bearer" + } + }, + new string[]{} + } + }); +}); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); @@ -10,19 +77,31 @@ var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { + app.UseWebAssemblyDebugging(); + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyBlazor v1"); + c.RoutePrefix = "api/docs"; + }); } else { + app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } - app.UseHttpsRedirection(); + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); +app.UseCors("CorsPolicy"); + +app.UseAuthentication(); +app.UseAuthorization(); app.MapRazorPages(); app.MapControllers(); diff --git a/Server/appsettings.json b/Server/appsettings.json index 10f68b8..7860b5e 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -5,5 +5,13 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" -} + "ConnectionStrings": { + "Connection": "server=54.37.19.162;database=dev_biskilogclients;user=biskilog;password=mefbuk-6niFsu-fytrew" + }, + "AllowedHosts": "*", + "JWT": { + "Key": "@@BISKILOG2023DEV??//##$", + "Issuer": "AUTH SERVER", + "Audience": "BISKILOG" + } +} \ No newline at end of file diff --git a/Shared/Biskilog Accounting.Shared.csproj b/Shared/Biskilog Accounting.Shared.csproj index 34ee232..a747b67 100644 --- a/Shared/Biskilog Accounting.Shared.csproj +++ b/Shared/Biskilog Accounting.Shared.csproj @@ -15,7 +15,6 @@ - diff --git a/Shared/Interfaces/IAuthService.cs b/Shared/Interfaces/IAuthService.cs new file mode 100644 index 0000000..9d5b81f --- /dev/null +++ b/Shared/Interfaces/IAuthService.cs @@ -0,0 +1,25 @@ +using Biskilog_Accounting.Shared.ClientContractModels; + +namespace Biskilog_Accounting.Shared.Interfaces +{ + public interface IAuthService + { + /// + /// Prepares and returns the connection string for a client using the specified database id + /// + /// Specified database id to use + /// + string GetClientConnectionString(int a_databaseId); + /// + /// Authenticates user or client + /// + /// + /// + /// A tokenized string with relevant information on the authenticated user + Task AuthenticateClient(string a_username, string a_password); + Contract GetContract(int a_clientId, int a_businessId); + Databasemap GetClientDB(int a_clientId); + Siteaccesspermission GetSiteaccesspermission(int a_clientId); + Clientbusiness GetClientbusiness(int a_clientId); + } +} diff --git a/Shared/Interfaces/ITokenService.cs b/Shared/Interfaces/ITokenService.cs new file mode 100644 index 0000000..b4c03f2 --- /dev/null +++ b/Shared/Interfaces/ITokenService.cs @@ -0,0 +1,15 @@ +using Biskilog_Accounting.Shared.ClientContractModels; + + +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); + int? GetUserIdFromToken(string a_token); + string? GetUserNameFromToken(string a_token); + + } +} From 45ab3dee38990be82b27d39cd2d8330137f277fd Mon Sep 17 00:00:00 2001 From: barhen Date: Sun, 21 May 2023 14:02:05 -0500 Subject: [PATCH 2/2] Backend Login Feature commit 21_05_2023 --- Client/Pages/Auth/Login.razor.cs | 7 + ...vBiskacdbContext.cs => BiskAcdbContext.cs} | 10 +- Server/Biskilog Accounting.Server.csproj | 5 +- ...ogClientsContext.cs => BiskilogContext.cs} | 20 ++- .../Controllers/AuthenticationController.cs | 39 +++++ Server/Program.cs | 8 +- Server/Services/AuthenticationService.cs | 141 ++++++++++++++++++ Server/Services/ConnectionService.cs | 52 +++++++ Server/appsettings.json | 5 +- Shared/Biskilog Accounting.Shared.csproj | 8 +- Shared/ClientContractModels/Databasemap.cs | 3 + Shared/Enums/AuthEnums.cs | 17 +++ Shared/Enums/ConnectionEnums.cs | 14 ++ Shared/Interfaces/IAuthService.cs | 12 +- Shared/Interfaces/IConnectionService.cs | 24 +++ Shared/Interfaces/ITokenService.cs | 8 +- Shared/ServiceRepo/TokenService.cs | 124 +++++++++++++++ 17 files changed, 460 insertions(+), 37 deletions(-) create mode 100644 Client/Pages/Auth/Login.razor.cs rename Server/{DevBiskacdbContext.cs => BiskAcdbContext.cs} (98%) rename Server/{BiskilogClientsContext.cs => BiskilogContext.cs} (93%) create mode 100644 Server/Controllers/AuthenticationController.cs create mode 100644 Server/Services/AuthenticationService.cs create mode 100644 Server/Services/ConnectionService.cs create mode 100644 Shared/Enums/AuthEnums.cs create mode 100644 Shared/Enums/ConnectionEnums.cs create mode 100644 Shared/Interfaces/IConnectionService.cs create mode 100644 Shared/ServiceRepo/TokenService.cs diff --git a/Client/Pages/Auth/Login.razor.cs b/Client/Pages/Auth/Login.razor.cs new file mode 100644 index 0000000..cc6edb0 --- /dev/null +++ b/Client/Pages/Auth/Login.razor.cs @@ -0,0 +1,7 @@ +namespace Biskilog_Accounting.Client.Pages.Auth +{ + public partial class Login + { + + } +} diff --git a/Server/DevBiskacdbContext.cs b/Server/BiskAcdbContext.cs similarity index 98% rename from Server/DevBiskacdbContext.cs rename to Server/BiskAcdbContext.cs index f8ce30f..10468ab 100644 --- a/Server/DevBiskacdbContext.cs +++ b/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 options) + public BiskAcdbContext(DbContextOptions options) : base(options) { } @@ -76,10 +76,6 @@ public partial class DevBiskacdbContext : DbContext public virtual DbSet 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 diff --git a/Server/Biskilog Accounting.Server.csproj b/Server/Biskilog Accounting.Server.csproj index f10e87f..57e689c 100644 --- a/Server/Biskilog Accounting.Server.csproj +++ b/Server/Biskilog Accounting.Server.csproj @@ -15,6 +15,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -35,8 +36,4 @@ - - - - diff --git a/Server/BiskilogClientsContext.cs b/Server/BiskilogContext.cs similarity index 93% rename from Server/BiskilogClientsContext.cs rename to Server/BiskilogContext.cs index d009d98..0795270 100644 --- a/Server/BiskilogClientsContext.cs +++ b/Server/BiskilogContext.cs @@ -4,14 +4,15 @@ using Biskilog_Accounting.Shared.ClientContractModels; using Microsoft.EntityFrameworkCore; namespace Biskilog_Accounting.Server; - -public partial class BiskilogClientsContext : DbContext +/// +/// This is the main EF DbContext for the Biskilog Accounting +/// +public partial class BiskilogContext : DbContext { - public BiskilogClientsContext() + public BiskilogContext() { } - - public BiskilogClientsContext(DbContextOptions options) + public BiskilogContext(DbContextOptions 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(entity => diff --git a/Server/Controllers/AuthenticationController.cs b/Server/Controllers/AuthenticationController.cs new file mode 100644 index 0000000..41b6d75 --- /dev/null +++ b/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 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(); + } + } + } +} diff --git a/Server/Program.cs b/Server/Program.cs index 8a53ee1..b898305 100644 --- a/Server/Program.cs +++ b/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(options => +builder.Services.AddEntityFrameworkMySql().AddDbContext(options => { options.UseMySql(builder.Configuration.GetConnectionString("Connection"), new MariaDbServerVersion(new Version())); }); -//builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddCors(options => { diff --git a/Server/Services/AuthenticationService.cs b/Server/Services/AuthenticationService.cs new file mode 100644 index 0000000..172071b --- /dev/null +++ b/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; + } + + /// + /// Returns the status of a user account + /// + /// AuthEnums + 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; + } + } + /// + /// Autenticates a user and returns a tokenized string + /// + /// + /// + /// strings + + public async Task 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 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); + } + + /// + /// Creates a new user account + /// + /// 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; + } + + /// + /// Deletes a user account + /// + /// + public void DeleteUser(int a_id) + { + throw new NotImplementedException(); + } + + public List 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 a_businessId) + { + return m_context.Contracts.FirstOrDefault(c => c.ClientId == a_clientId && a_businessId.Contains(c.BusinessId!.Value) && c.EndDate >= DateTime.Now); + } + + public List 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 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; + //} + } + } +} diff --git a/Server/Services/ConnectionService.cs b/Server/Services/ConnectionService.cs new file mode 100644 index 0000000..fa89b02 --- /dev/null +++ b/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; + } + /// + /// Prepares and returns the connection string for a client using the specified database id + /// + /// Specified database id to use + /// + 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(); + } + /// + /// Prepare the DB context from the specified connection string + /// + /// + /// + /// A configured BiskAcdbContext + public object PrepareDBContext(string a_connectionString) + { + + DbContextOptionsBuilder acdbContext = new DbContextOptionsBuilder(); + acdbContext.UseMySql(a_connectionString, new MariaDbServerVersion(new Version())); + + return acdbContext; + } + } +} diff --git a/Server/appsettings.json b/Server/appsettings.json index 7860b5e..140c6ab 100644 --- a/Server/appsettings.json +++ b/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" } diff --git a/Shared/Biskilog Accounting.Shared.csproj b/Shared/Biskilog Accounting.Shared.csproj index a747b67..b5a3a0f 100644 --- a/Shared/Biskilog Accounting.Shared.csproj +++ b/Shared/Biskilog Accounting.Shared.csproj @@ -8,13 +8,13 @@ + + + + - - - - diff --git a/Shared/ClientContractModels/Databasemap.cs b/Shared/ClientContractModels/Databasemap.cs index b4ad7a1..efd3d14 100644 --- a/Shared/ClientContractModels/Databasemap.cs +++ b/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; } } diff --git a/Shared/Enums/AuthEnums.cs b/Shared/Enums/AuthEnums.cs new file mode 100644 index 0000000..b656598 --- /dev/null +++ b/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 + } +} + diff --git a/Shared/Enums/ConnectionEnums.cs b/Shared/Enums/ConnectionEnums.cs new file mode 100644 index 0000000..70821de --- /dev/null +++ b/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, + } +} diff --git a/Shared/Interfaces/IAuthService.cs b/Shared/Interfaces/IAuthService.cs index 9d5b81f..70a8960 100644 --- a/Shared/Interfaces/IAuthService.cs +++ b/Shared/Interfaces/IAuthService.cs @@ -4,12 +4,6 @@ namespace Biskilog_Accounting.Shared.Interfaces { public interface IAuthService { - /// - /// Prepares and returns the connection string for a client using the specified database id - /// - /// Specified database id to use - /// - string GetClientConnectionString(int a_databaseId); /// /// Authenticates user or client /// @@ -17,9 +11,9 @@ namespace Biskilog_Accounting.Shared.Interfaces /// /// A tokenized string with relevant information on the authenticated user Task AuthenticateClient(string a_username, string a_password); - Contract GetContract(int a_clientId, int a_businessId); + Contract? GetContract(int a_clientId, List a_businessId); Databasemap GetClientDB(int a_clientId); - Siteaccesspermission GetSiteaccesspermission(int a_clientId); - Clientbusiness GetClientbusiness(int a_clientId); + List GetSiteaccesspermission(int a_clientId, int a_userId); + List GetClientbusiness(int a_clientId); } } diff --git a/Shared/Interfaces/IConnectionService.cs b/Shared/Interfaces/IConnectionService.cs new file mode 100644 index 0000000..0684c3a --- /dev/null +++ b/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 + { + /// + /// Prepares and returns the connection string for a client using the specified database id + /// + /// Specified database id to use + /// + string GetClientConnectionString(int a_databaseId); + /// + /// Prepare the DB context from the specified connection string + /// + /// + /// A configured BiskAcdbContext + object PrepareDBContext(string a_connectionString); + } +} diff --git a/Shared/Interfaces/ITokenService.cs b/Shared/Interfaces/ITokenService.cs index b4c03f2..730926f 100644 --- a/Shared/Interfaces/ITokenService.cs +++ b/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); diff --git a/Shared/ServiceRepo/TokenService.cs b/Shared/ServiceRepo/TokenService.cs new file mode 100644 index 0000000..8d3639a --- /dev/null +++ b/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; + } + + /// + /// Validates a user access token + /// + /// AuthEnums.Valid if token is a valid and unexpired token + 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; + } + } + /// + /// Generates an access token based on the user + /// + /// A tokenized string + 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(); + } + } + /// + ///Deserializes the token string if valid to return the specified user role id in the token string + /// + /// + /// RoleId + 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; + } + /// + ///Deserializes the token string if valid to return the specified user id in the token string + /// + /// + /// UserId + 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; + } + /// + ///Deserializes the token string if valid to return the specified username in the token string + /// + /// + /// Username + 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; + } + } +}