Added backend

This commit is contained in:
Sven Heidemann 2022-02-20 19:04:11 +01:00
commit b789a8f8bf
78 changed files with 4382 additions and 0 deletions

66
.gitignore vendored Normal file
View File

@ -0,0 +1,66 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
*/dist
*/tmp
*/out-tsc
# Only exists if Bazel was run
*/bazel-out
# dependencies
*node_modules*
# profiling files
*chrome-profiler-events*.json
*speed-measure-plugin*.json
# IDEs and editors
*/.idea
*.project
*.classpath
*.c9/
*.launch
*.settings/
*.sublime-workspace
# IDE - VSCode
*.vscode/*
!*.vscode/settings.json
!*.vscode/tasks.json
!*.vscode/launch.json
!*.vscode/extensions.json
*.history/*
# misc
*/.sass-cache
*/connect.lock
*/coverage
*/libpeerconnection.log
*npm-debug.log
*yarn-error.log
*testem.log
*/typings
# System Files
*.DS_Store
*Thumbs.db
# .Net Env
*Debug*
obj
logs
-nlog.txt
.vs
.vscode
# Python Env
*__pycache__*
*.pyc
*.idea*
*.log
# angular & .net
dist
bin
.angular
appsettings.*.json

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Sven Heidemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# GSWI Server

View File

@ -0,0 +1,10 @@
using System;
namespace gswi.Configuration
{
public class APISettings
{
public bool RedirectToHTTPS { get; set; }
public APIVersionSettings ApiVersion { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace gswi.Configuration
{
public class APIVersionSettings
{
public string Major { get; set; }
public string Minor { get; set; }
public string Micro { get; set; }
public override string ToString() {
return $"{this.Major}.{this.Minor}.{this.Micro}";
}
}
}

View File

@ -0,0 +1,11 @@
namespace gswi.Configuration
{
public class AuthentificationSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int TokenExpireTime { get; set; }
public int RefreshTokenExpireTime { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using Microsoft.Extensions.Configuration;
using System;
namespace gswi.Configuration {
public static class ConfigurationExtensions {
public static TSetting GetSetting<TSetting>(this IConfiguration config, string sectionName)
where TSetting : new() {
var settings = new TSetting();
config.GetSection(sectionName).Bind(settings);
return settings;
}
public static string GetDbConnectionString(this IConfiguration config, string sectionName) {
var connectionString = config[$"{sectionName}:ConnectionString"];
if (string.IsNullOrEmpty(connectionString))
throw new Exception($"ConnectionString is not set! SectionName:{sectionName}");
var dbCredentials = config[$"{sectionName}:Credentials"];
if (!string.IsNullOrEmpty(dbCredentials))
connectionString += dbCredentials;
return connectionString;
}
public static string GetCustomer(this IConfiguration config) {
return config["CUSTOMER"];
}
public static string GetComputerName(this IConfiguration config) {
return config["COMPUTERNAME"];
}
}
}

View File

@ -0,0 +1,7 @@
namespace gswi.Configuration {
public static class Constants {
public const string CustomerEnvironmentVariable = "CUSTOMER";
public const string ComputerNameEnvironmentVariable = "COMPUTERNAME";
public const string CustomersDirectoryName = "customers";
}
}

View File

@ -0,0 +1,8 @@
namespace gswi.Configuration
{
public class DatabaseSettings
{
public string ConnectionString { get; set; }
public string Credentials { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace gswi.Configuration
{
public class EMailSettings
{
public string FromName { get; set; }
public string FromAddress { get; set; }
public string MailServerAddress { get; set; }
public int MailServerPort { get; set; }
public string Username { get; set; }
public string Credentials { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace gswi.Configuration
{
public class FrontendSettings
{
public string URL { get; set; }
}
}

View File

@ -0,0 +1,115 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace gswi.Configuration {
public static class HostExtensions {
// copied from https://github.com/dotnet/extensions/blob/release/3.1/src/Hosting/Hosting/src/Host.cs,
// modified to use customer environment
public static IHostBuilder CreateBuilder(string[] args) {
var builder = new HostBuilder();
builder.UseContentRoot(Directory.GetCurrentDirectory()); // UseWindowsService overrides content root to AppContext.BaseDirectory
builder.ConfigureHostConfiguration(config => {
config.AddEnvironmentVariables(prefix: "DOTNET_");
config.AddEnvironmentVariables(prefix: "LOGINCOUNTER_");
if (args != null) {
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
var customerEnv = hostingContext.Configuration.GetValue<string>(Constants.CustomerEnvironmentVariable);
//var customerEnv = Environment.GetEnvironmentVariable(Constants.CustomerEnvironmentVariable);
if (!string.IsNullOrEmpty(customerEnv))
config.SetBasePath(Path.Combine(hostingContext.HostingEnvironment.ContentRootPath, Constants.CustomersDirectoryName, customerEnv));
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true);
var computerName = Environment.GetEnvironmentVariable(Constants.ComputerNameEnvironmentVariable);
if (!string.IsNullOrEmpty(computerName))
config.AddJsonFile($"appsettings.{computerName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName)) {
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null) {
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
config.AddEnvironmentVariables("IOTHUB_");
if (args != null) {
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) => {
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// IMPORTANT: This needs to be added *before* configuration is loaded, this lets
// the defaults be overridden by the configuration.
if (isWindows) {
// Default the EventLogLoggerProvider to warning or above
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (isWindows) {
// Add the EventLogLoggerProvider on windows machines
logging.AddEventLog();
}
})
.UseDefaultServiceProvider((context, options) => {
var isDevelopment = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
public static void LogHostingEnvironment<T>(this IHost host) {
var loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
var configuration = host.Services.GetRequiredService<IConfiguration>();
var hostEnvironment = host.Services.GetRequiredService<IHostEnvironment>();
var logger = loggerFactory.CreateLogger<T>();
logger.LogInformation("Running Host...");
logger.LogInformation($"EnvironmentName:{hostEnvironment.EnvironmentName}");
logger.LogInformation($"ContentRootPath:{hostEnvironment.ContentRootPath}");
logger.LogInformation($"CurrentDirectory:{Environment.CurrentDirectory}");
logger.LogInformation($"ApplicationName:{hostEnvironment.ApplicationName}");
logger.LogInformation($"Customer:{configuration.GetCustomer()}");
logger.LogInformation($"ComputerName:{configuration.GetComputerName()}");
}
public static void LogStartError(Exception ex) {
var logDir = Path.Combine(AppContext.BaseDirectory, "logs");
if (!Directory.Exists(logDir))
Directory.CreateDirectory(logDir);
var logFile = Path.Combine(logDir, "start_error.txt");
File.AppendAllText(logFile, DateTime.Now.ToString() + ": " + ex.ToString() + Environment.NewLine);
}
public static void ChangeCurrentDirToProjectDir() {
var currentDir = Directory.GetCurrentDirectory();
var ix = currentDir.IndexOf("\\bin"); // goto project dir, if we are running from VS
if (ix >= 0) {
var newDir = currentDir.Substring(0, ix);
Directory.SetCurrentDirectory(newDir);
}
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1"/>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0"/>
<PackageReference Include="NLog" Version="4.7.13"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
using System;
namespace gswi.CredentialManager
{
public static class Base64
{
public static string Encode(string credentials)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(credentials);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Decode(string credentials)
{
var base64EncodedBytes = System.Convert.FromBase64String(credentials);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string DecodeConnectionString(string connectionString, string credentials)
{
string decodedCredentials = Decode(credentials);
return connectionString += $"password={decodedCredentials};";
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.IO;
using Microsoft.Extensions.Configuration;
using gswi.Configuration;
namespace gswi.CredentialManager
{
class Program
{
static IConfiguration Configuration { get; set; }
static void Menu()
{
bool end = false;
while (!end)
{
Console.Clear();
Console.WriteLine("Credential Manager: \n");
Console.WriteLine("[1] Encode");
Console.WriteLine("[2] Decode");
Console.WriteLine("[x] Exit");
Console.Write("\n> ");
string input = Console.ReadLine();
switch (input)
{
case "1":
Console.Clear();
Encode();
break;
case "2":
Console.Clear();
Decode();
break;
case "x":
end = true;
Continue();
break;
case "X":
end = true;
Continue();
break;
default:
Console.WriteLine("Invalid input");
Continue();
break;
}
}
}
static void Continue()
{
Console.WriteLine("\n\nPress any key to continue...");
Console.ReadLine();
}
static void Main(string[] args)
{
Menu();
}
static void Encode()
{
Console.Write("String to encode: ");
string input = Console.ReadLine();
Console.WriteLine($"{input} encoded with Base64 is: {Base64.Encode(input)}");
Continue();
}
static void Decode()
{
Console.Write("String to decode: ");
string input = Console.ReadLine();
Console.WriteLine($"{input} decoded with Base64 is: {Base64.Decode(input)}");
Continue();
}
protected static SettingType GetTypedSettingsFromSection<SettingType>(string sectionName) where SettingType : new()
{
var settings = new SettingType();
Configuration.GetSection(sectionName).Bind(settings);
return settings;
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0"/>
<PackageReference Include="NLog" Version="4.7.13"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gswi.Configuration\gswi.Configuration.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using gswi.Interface.Repositories;
using gswi.Model;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace gswi.Data
{
public class DatabaseContext : DbContext, IUnitOfWork
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<AuthUser> AuthUsers { get; set; }
public Task<int> SaveChangesAsync()
{
var changedList = ChangeTracker.Entries<IAutoGenerateDateFields>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var item in changedList)
{
item.Entity.LastModifiedOn = DateTime.UtcNow;
if (item.State == EntityState.Added)
item.Entity.CreatedOn = DateTime.UtcNow;
}
return base.SaveChangesAsync();
}
public override int SaveChanges()
{
var changedList = ChangeTracker.Entries<IAutoGenerateDateFields>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var item in changedList)
{
item.Entity.LastModifiedOn = DateTime.UtcNow;
if (item.State == EntityState.Added)
item.Entity.CreatedOn = DateTime.UtcNow;
}
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// Datum der letzten Änderung, etc. setzen
DateTimeOffset now = DateTimeOffset.UtcNow;
var changedList = ChangeTracker.Entries<IAutoGenerateDateFields>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var item in changedList)
{
item.Entity.LastModifiedOn = now;
if (item.State == EntityState.Added)
item.Entity.CreatedOn = now;
}
return base.SaveChangesAsync();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<AuthUser>()
.HasIndex(au => au.EMail)
.IsUnique(true);
modelBuilder.Entity<AuthUser>()
.HasIndex(au => au.ConfirmationId)
.IsUnique(true);
modelBuilder.Entity<AuthUser>()
.HasIndex(au => au.ForgotPasswordId)
.IsUnique(true);
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using System;
using System.Collections.Generic;
using System.Text;
namespace gswi.Data
{
public class DesignTimeContextFactory : IDesignTimeDbContextFactory<DatabaseContext>
{
public DatabaseContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
optionsBuilder.UseMySql(
"server=localhost;user=sh_login_counter;password=mZaKBHY1lhzQoCAv;database=sh_login_counter",
new MySqlServerVersion(new Version(10, 3, 32))
);
return new DatabaseContext(optionsBuilder.Options);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Linq;
using System.Linq.Expressions;
namespace gswi.Data
{
public static class LinqExtension
{
public static IQueryable<T> OrderByMember<T>(this IQueryable<T> q, string SortField)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), "OrderBy", types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
public static IQueryable<T> OrderByMemberDescending<T>(this IQueryable<T> q, string SortField)
{
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), "OrderByDescending", types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
}
}

View File

@ -0,0 +1,291 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using gswi.Data;
namespace gswi.Data.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20210213133159_2021_03_10")]
partial class _2021_03_10
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("gswi.Model.AuthUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<string>("ConfirmationId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("EMail")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<string>("FirstName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("ForgotPasswordId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("LastName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Password")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("RefreshToken")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("RefreshTokenExpiryTime")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("ConfirmationId")
.IsUnique();
b.HasIndex("EMail")
.IsUnique();
b.HasIndex("ForgotPasswordId")
.IsUnique();
b.ToTable("AuthUsers");
});
modelBuilder.Entity("gswi.Model.Domain", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Domains");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.Property<byte[]>("IPAddress")
.HasColumnType("varbinary(1400)")
.HasMaxLength(1400);
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.Property<long>("OSId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DomainId");
b.HasIndex("OSId");
b.HasIndex("DomainId", "Name")
.IsUnique();
b.HasIndex("IPAddress", "Name")
.IsUnique();
b.ToTable("Hosts");
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long>("HostId")
.HasColumnType("bigint");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("Time")
.HasColumnType("datetime(6)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("HostId");
b.HasIndex("UserId");
b.ToTable("Logins");
});
modelBuilder.Entity("gswi.Model.OperatingSystem", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("OperatingSystems");
});
modelBuilder.Entity("gswi.Model.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("Discriminator", "Name")
.IsUnique();
b.ToTable("Users");
b.HasDiscriminator<string>("Discriminator").HasValue("User");
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.HasIndex("DomainId");
b.HasDiscriminator().HasValue("DomainUser");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("HostId")
.HasColumnType("bigint");
b.HasIndex("HostId");
b.HasDiscriminator().HasValue("HostUser");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Hosts")
.HasForeignKey("DomainId");
b.HasOne("gswi.Model.OperatingSystem", "OS")
.WithMany()
.HasForeignKey("OSId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Logins")
.HasForeignKey("HostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("gswi.Model.User", "User")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Users")
.HasForeignKey("DomainId");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Users")
.HasForeignKey("HostId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,249 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace gswi.Data.Migrations
{
public partial class _2021_03_10 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AuthUsers",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
FirstName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true),
EMail = table.Column<string>(nullable: true),
Password = table.Column<string>(nullable: true),
RefreshToken = table.Column<string>(nullable: true),
ConfirmationId = table.Column<string>(nullable: true),
ForgotPasswordId = table.Column<string>(nullable: true),
RefreshTokenExpiryTime = table.Column<DateTime>(nullable: false),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AuthUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Domains",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
NotifyWhenLogin = table.Column<bool>(nullable: false),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Domains", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OperatingSystems",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OperatingSystems", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Hosts",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
IPAddress = table.Column<byte[]>(maxLength: 1400, nullable: true),
NotifyWhenLogin = table.Column<bool>(nullable: false),
DomainId = table.Column<long>(nullable: true),
OSId = table.Column<long>(nullable: false),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Hosts", x => x.Id);
table.ForeignKey(
name: "FK_Hosts_Domains_DomainId",
column: x => x.DomainId,
principalTable: "Domains",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Hosts_OperatingSystems_OSId",
column: x => x.OSId,
principalTable: "OperatingSystems",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true),
NotifyWhenLogin = table.Column<bool>(nullable: false),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false),
Discriminator = table.Column<string>(nullable: false),
DomainId = table.Column<long>(nullable: true),
HostId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
table.ForeignKey(
name: "FK_Users_Domains_DomainId",
column: x => x.DomainId,
principalTable: "Domains",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Users_Hosts_HostId",
column: x => x.HostId,
principalTable: "Hosts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Logins",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Time = table.Column<DateTimeOffset>(nullable: false),
HostId = table.Column<long>(nullable: false),
UserId = table.Column<long>(nullable: false),
CreatedOn = table.Column<DateTimeOffset>(nullable: false),
LastModifiedOn = table.Column<DateTimeOffset>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Logins", x => x.Id);
table.ForeignKey(
name: "FK_Logins_Hosts_HostId",
column: x => x.HostId,
principalTable: "Hosts",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Logins_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AuthUsers_ConfirmationId",
table: "AuthUsers",
column: "ConfirmationId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AuthUsers_EMail",
table: "AuthUsers",
column: "EMail",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AuthUsers_ForgotPasswordId",
table: "AuthUsers",
column: "ForgotPasswordId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Hosts_DomainId",
table: "Hosts",
column: "DomainId");
migrationBuilder.CreateIndex(
name: "IX_Hosts_OSId",
table: "Hosts",
column: "OSId");
migrationBuilder.CreateIndex(
name: "IX_Hosts_DomainId_Name",
table: "Hosts",
columns: new[] { "DomainId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Hosts_IPAddress_Name",
table: "Hosts",
columns: new[] { "IPAddress", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Logins_HostId",
table: "Logins",
column: "HostId");
migrationBuilder.CreateIndex(
name: "IX_Logins_UserId",
table: "Logins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OperatingSystems_Name",
table: "OperatingSystems",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Users_DomainId",
table: "Users",
column: "DomainId");
migrationBuilder.CreateIndex(
name: "IX_Users_HostId",
table: "Users",
column: "HostId");
migrationBuilder.CreateIndex(
name: "IX_Users_Discriminator_Name",
table: "Users",
columns: new[] { "Discriminator", "Name" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AuthUsers");
migrationBuilder.DropTable(
name: "Logins");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "Hosts");
migrationBuilder.DropTable(
name: "Domains");
migrationBuilder.DropTable(
name: "OperatingSystems");
}
}
}

View File

@ -0,0 +1,294 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using gswi.Data;
namespace gswi.Data.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20210217201310_2021_03_13")]
partial class _2021_03_13
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("gswi.Model.AuthUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<int>("AuthRole")
.HasColumnType("int");
b.Property<string>("ConfirmationId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("EMail")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<string>("FirstName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("ForgotPasswordId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("LastName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Password")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("RefreshToken")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("RefreshTokenExpiryTime")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("ConfirmationId")
.IsUnique();
b.HasIndex("EMail")
.IsUnique();
b.HasIndex("ForgotPasswordId")
.IsUnique();
b.ToTable("AuthUsers");
});
modelBuilder.Entity("gswi.Model.Domain", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Domains");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.Property<byte[]>("IPAddress")
.HasColumnType("varbinary(1400)")
.HasMaxLength(1400);
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.Property<long>("OSId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DomainId");
b.HasIndex("OSId");
b.HasIndex("DomainId", "Name")
.IsUnique();
b.HasIndex("IPAddress", "Name")
.IsUnique();
b.ToTable("Hosts");
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long>("HostId")
.HasColumnType("bigint");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("Time")
.HasColumnType("datetime(6)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("HostId");
b.HasIndex("UserId");
b.ToTable("Logins");
});
modelBuilder.Entity("gswi.Model.OperatingSystem", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("OperatingSystems");
});
modelBuilder.Entity("gswi.Model.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("Discriminator", "Name")
.IsUnique();
b.ToTable("Users");
b.HasDiscriminator<string>("Discriminator").HasValue("User");
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.HasIndex("DomainId");
b.HasDiscriminator().HasValue("DomainUser");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("HostId")
.HasColumnType("bigint");
b.HasIndex("HostId");
b.HasDiscriminator().HasValue("HostUser");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Hosts")
.HasForeignKey("DomainId");
b.HasOne("gswi.Model.OperatingSystem", "OS")
.WithMany()
.HasForeignKey("OSId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Logins")
.HasForeignKey("HostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("gswi.Model.User", "User")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Users")
.HasForeignKey("DomainId");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Users")
.HasForeignKey("HostId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace gswi.Data.Migrations
{
public partial class _2021_03_13 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AuthRole",
table: "AuthUsers",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AuthRole",
table: "AuthUsers");
}
}
}

View File

@ -0,0 +1,291 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using gswi.Data;
namespace gswi.Data.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20210220140309_2021_03_14")]
partial class _2021_03_14
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("gswi.Model.AuthUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<int>("AuthRole")
.HasColumnType("int");
b.Property<string>("ConfirmationId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("EMail")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<string>("FirstName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("ForgotPasswordId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("LastName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Password")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("RefreshToken")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("RefreshTokenExpiryTime")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("ConfirmationId")
.IsUnique();
b.HasIndex("EMail")
.IsUnique();
b.HasIndex("ForgotPasswordId")
.IsUnique();
b.ToTable("AuthUsers");
});
modelBuilder.Entity("gswi.Model.Domain", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Domains");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.Property<byte[]>("IPAddress")
.HasColumnType("varbinary(1400)")
.HasMaxLength(1400);
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.Property<long>("OSId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DomainId");
b.HasIndex("OSId");
b.HasIndex("DomainId", "Name")
.IsUnique();
b.HasIndex("IPAddress", "Name")
.IsUnique();
b.ToTable("Hosts");
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long>("HostId")
.HasColumnType("bigint");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("Time")
.HasColumnType("datetime(6)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("HostId");
b.HasIndex("UserId");
b.ToTable("Logins");
});
modelBuilder.Entity("gswi.Model.OperatingSystem", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("OperatingSystems");
});
modelBuilder.Entity("gswi.Model.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Users");
b.HasDiscriminator<string>("Discriminator").HasValue("User");
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.HasIndex("DomainId");
b.HasDiscriminator().HasValue("DomainUser");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("HostId")
.HasColumnType("bigint");
b.HasIndex("HostId");
b.HasDiscriminator().HasValue("HostUser");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Hosts")
.HasForeignKey("DomainId");
b.HasOne("gswi.Model.OperatingSystem", "OS")
.WithMany()
.HasForeignKey("OSId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Logins")
.HasForeignKey("HostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("gswi.Model.User", "User")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Users")
.HasForeignKey("DomainId");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Users")
.HasForeignKey("HostId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace gswi.Data.Migrations
{
public partial class _2021_03_14 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_Discriminator_Name",
table: "Users");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Users",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(255) CHARACTER SET utf8mb4",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Discriminator",
table: "Users",
nullable: false,
oldClrType: typeof(string),
oldType: "varchar(255) CHARACTER SET utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Users",
type: "varchar(255) CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Discriminator",
table: "Users",
type: "varchar(255) CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string));
migrationBuilder.CreateIndex(
name: "IX_Users_Discriminator_Name",
table: "Users",
columns: new[] { "Discriminator", "Name" },
unique: true);
}
}
}

View File

@ -0,0 +1,289 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using gswi.Data;
namespace gswi.Data.Migrations
{
[DbContext(typeof(DatabaseContext))]
partial class DatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("gswi.Model.AuthUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<int>("AuthRole")
.HasColumnType("int");
b.Property<string>("ConfirmationId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("EMail")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<string>("FirstName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("ForgotPasswordId")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("LastName")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Password")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("RefreshToken")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("RefreshTokenExpiryTime")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("ConfirmationId")
.IsUnique();
b.HasIndex("EMail")
.IsUnique();
b.HasIndex("ForgotPasswordId")
.IsUnique();
b.ToTable("AuthUsers");
});
modelBuilder.Entity("gswi.Model.Domain", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Domains");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.Property<byte[]>("IPAddress")
.HasColumnType("varbinary(1400)")
.HasMaxLength(1400);
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.Property<long>("OSId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("DomainId");
b.HasIndex("OSId");
b.HasIndex("DomainId", "Name")
.IsUnique();
b.HasIndex("IPAddress", "Name")
.IsUnique();
b.ToTable("Hosts");
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<long>("HostId")
.HasColumnType("bigint");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("Time")
.HasColumnType("datetime(6)");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("HostId");
b.HasIndex("UserId");
b.ToTable("Logins");
});
modelBuilder.Entity("gswi.Model.OperatingSystem", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("OperatingSystems");
});
modelBuilder.Entity("gswi.Model.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<DateTimeOffset>("CreatedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTimeOffset>("LastModifiedOn")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("NotifyWhenLogin")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("Users");
b.HasDiscriminator<string>("Discriminator").HasValue("User");
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("DomainId")
.HasColumnType("bigint");
b.HasIndex("DomainId");
b.HasDiscriminator().HasValue("DomainUser");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasBaseType("gswi.Model.User");
b.Property<long?>("HostId")
.HasColumnType("bigint");
b.HasIndex("HostId");
b.HasDiscriminator().HasValue("HostUser");
});
modelBuilder.Entity("gswi.Model.Host", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Hosts")
.HasForeignKey("DomainId");
b.HasOne("gswi.Model.OperatingSystem", "OS")
.WithMany()
.HasForeignKey("OSId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.Login", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Logins")
.HasForeignKey("HostId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("gswi.Model.User", "User")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("gswi.Model.DomainUser", b =>
{
b.HasOne("gswi.Model.Domain", "Domain")
.WithMany("Users")
.HasForeignKey("DomainId");
});
modelBuilder.Entity("gswi.Model.HostUser", b =>
{
b.HasOne("gswi.Model.Host", "Host")
.WithMany("Users")
.HasForeignKey("HostId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,125 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using gswi.Interface.Repositories;
using gswi.Model;
using gswi.Share.Common;
namespace gswi.Data.Repositories
{
public class AuthUserRepositoryImpl : IAuthUserRepository
{
private readonly DatabaseContext _databaseContext;
private DbSet<AuthUser> _authUsers { get { return _databaseContext.AuthUsers; } }
public AuthUserRepositoryImpl(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public async Task<List<AuthUser>> GetAllAuthUsersAsync()
{
return await _authUsers
.ToListAsync();
}
public async Task<(List<AuthUser>, int totalCount)> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion)
{
var query = _authUsers
.Select(u => new AuthUser
{
Id = u.Id,
FirstName = u.FirstName,
LastName = u.LastName,
EMail = u.EMail,
ConfirmationId = u.ConfirmationId,
AuthRole = u.AuthRole
});
// FirstName filter
if (!string.IsNullOrWhiteSpace(selectCriterion.FirstName))
query = query.Where(u => u.FirstName.Contains(selectCriterion.FirstName) || u.FirstName == selectCriterion.FirstName);
// LastName filter
if (!string.IsNullOrWhiteSpace(selectCriterion.LastName))
query = query.Where(u => u.LastName.Contains(selectCriterion.LastName) || u.LastName == selectCriterion.LastName);
// EMail filter
if (!string.IsNullOrWhiteSpace(selectCriterion.EMail))
query = query.Where(u => u.EMail.Contains(selectCriterion.EMail) || u.EMail == selectCriterion.EMail);
// AuthRole filter
if (selectCriterion.AuthRole != null)
query = query.Where(u => u.AuthRole == (AuthRoles)selectCriterion.AuthRole);
// sort
if (!string.IsNullOrWhiteSpace(selectCriterion.SortColumn) && !string.IsNullOrWhiteSpace(selectCriterion.SortDirection))
{
var critSortDirection = selectCriterion.SortDirection?.ToLower();
if (critSortDirection == "desc" || critSortDirection == "descending")
query = query.OrderByMemberDescending(selectCriterion.SortColumn);
else
query = query.OrderByMember(selectCriterion.SortColumn);
}
var skip = selectCriterion.PageSize * selectCriterion.PageIndex;
if (skip < 0)
skip = 0;
var totalCount = await query.CountAsync();
var list = await query.Skip(skip).Take(selectCriterion.PageSize).AsNoTracking().ToListAsync();
return (list, totalCount);
}
public async Task<AuthUser> GetAuthUserByEMailAsync(string email)
{
return await _authUsers
.Where(au => au.EMail == email)
.FirstAsync();
}
public async Task<AuthUser> FindAuthUserByEMailAsync(string email)
{
return await _authUsers
.Where(au => au.EMail == email)
.FirstOrDefaultAsync();
}
public async Task<AuthUser> FindAuthUserByEMailConfirmationIdAsync(string id)
{
return await _authUsers
.Where(au => au.ConfirmationId == id)
.FirstOrDefaultAsync();
}
public async Task<AuthUser> FindAuthUserByEMailForgotPasswordIdAsync(string id)
{
return await _authUsers
.Where(au => au.ForgotPasswordId == id)
.FirstOrDefaultAsync();
}
public void AddAuthUser(AuthUser user)
{
_authUsers.Add(user);
}
public async Task DeleteAuthUserByEMailAsync(string email)
{
var au = await _authUsers
.Where(au => au.EMail == email)
.FirstAsync();
_authUsers.Remove(au);
}
public void DeleteAuthUser(AuthUser user)
{
_authUsers.Remove(user);
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLog" Version="4.7.13"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gswi.Model\gswi.Model.csproj"/>
<ProjectReference Include="..\gswi.Interface\gswi.Interface.csproj"/>
<ProjectReference Include="..\gswi.Share.Common\gswi.Share.Common.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using gswi.Model;
using gswi.Share.Common;
namespace gswi.Interface.Repositories
{
public interface IAuthUserRepository
{
Task<List<AuthUser>> GetAllAuthUsersAsync();
Task<(List<AuthUser>, int totalCount)> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion);
Task<AuthUser> GetAuthUserByEMailAsync(string email);
Task<AuthUser> FindAuthUserByEMailAsync(string email);
Task<AuthUser> FindAuthUserByEMailConfirmationIdAsync(string id);
Task<AuthUser> FindAuthUserByEMailForgotPasswordIdAsync(string id);
void AddAuthUser(AuthUser user);
Task DeleteAuthUserByEMailAsync(string email);
void DeleteAuthUser(AuthUser user);
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace gswi.Interface.Repositories {
public interface IUnitOfWork : IDisposable {
int SaveChanges();
Task<int> SaveChangesAsync();
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading.Tasks;
using gswi.Model.DTOs;
using gswi.Share.Common;
namespace gswi.Interface.Services
{
public interface IAuthService
{
Task<List<AuthUserDTO>> GetAllAuthUsersAsync();
Task<GetFilteredAuthUsersResultDTO> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion);
Task<AuthUserDTO> GetAuthUserByEMailAsync(string email);
Task<AuthUserDTO> FindAuthUserByEMailAsync(string email);
Task<long> AddAuthUserAsync(AuthUserDTO userDTO);
Task<bool> ConfirmEMail(string id);
Task<TokenDTO> Login(AuthUserDTO userDTO);
Task ForgotPassword(string email);
Task<EMailStringDTO> ConfirmForgotPassword(string id);
Task ResetPassword(ResetPasswordDTO rpDTO);
Task UpdateUser(UpdateUserDTO updateUserDTO);
Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO);
Task<TokenDTO> Refresh(TokenDTO tokenDTO);
Task Revoke(TokenDTO tokenDTO);
Task DeleteAuthUserByEMailAsync(string email);
Task DeleteAuthUserAsync(AuthUserDTO userDTO);
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gswi.Model\gswi.Model.csproj"/>
<ProjectReference Include="..\gswi.Share.Common\gswi.Share.Common.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="4.7.13"/>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

10
gswi.Model/AuthRoles.cs Normal file
View File

@ -0,0 +1,10 @@
using System;
namespace gswi.Model
{
public enum AuthRoles
{
Normal = 0,
Admin = 1
}
}

34
gswi.Model/AuthUser.cs Normal file
View File

@ -0,0 +1,34 @@
using System;
using gswi.Model.DTOs;
namespace gswi.Model
{
public class AuthUser : IAutoGenerateDateFields
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EMail { get; set; }
public string Password { get; set; }
public AuthRoles AuthRole { get; set; }
public string RefreshToken { get; set; }
public string ConfirmationId { get; set; }
public string ForgotPasswordId { get; set; }
public DateTime RefreshTokenExpiryTime { get; set; }
public DateTimeOffset CreatedOn { get; set; }
public DateTimeOffset LastModifiedOn { get; set; }
public AuthUserDTO ToAuthUserDTO()
{
return new AuthUserDTO
{
Id = this.Id,
FirstName = this.FirstName,
LastName = this.LastName,
EMail = this.EMail,
IsConfirmed = this.ConfirmationId == null,
AuthRole = this.AuthRole
};
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace gswi.Model.DTOs
{
public class AdminUpdateUserDTO
{
public AuthUserDTO AuthUserDTO { get; set; }
public AuthUserDTO NewAuthUserDTO { get; set; }
public bool ChangePassword { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace gswi.Model.DTOs
{
public class ApiVersionDTO
{
public string Major { get; set; }
public string Minor { get; set; }
public string Micro { get; set; }
}
}

View File

@ -0,0 +1,27 @@
namespace gswi.Model.DTOs
{
public class AuthUserDTO
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EMail { get; set; }
public string Password { get; set; }
public bool IsConfirmed { get; set; }
public AuthRoles AuthRole { get; set; }
public AuthUser ToAuthUser()
{
return new AuthUser
{
Id = this.Id,
FirstName = this.FirstName,
LastName = this.LastName,
EMail = this.EMail,
Password = this.Password,
AuthRole = this.AuthRole
};
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace gswi.Model.DTOs
{
public class EMailStringDTO
{
public string EMail { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace gswi.Model.DTOs
{
public class GetFilteredAuthUsersResultDTO
{
public List<AuthUserDTO> Users { get; set; }
public int TotalCount { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace gswi.Model.DTOs
{
public class ResetPasswordDTO
{
public string Id { get; set; }
public string Password { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace gswi.Model.DTOs
{
public class SettingsDTO
{
public string WebVersion { get; set; }
public string ApiVersion { get; set; }
public string ConfigPath { get; set; }
public string WebBaseURL { get; set; }
public string ApiBaseURL { get; set; }
public int TokenExpireTime { get; set; }
public int RefreshTokenExpireTime { get; set; }
public string MailUser { get; set; }
public int MailPort { get; set; }
public string MailHost { get; set; }
public string MailTransceiver { get; set; }
public string MailTransceiverAddress { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace gswi.Model.DTOs
{
public class TokenDTO
{
public string Token { get; set; }
public string RefreshToken { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace gswi.Model.DTOs
{
public class UpdateUserDTO
{
public AuthUserDTO AuthUserDTO { get; set; }
public AuthUserDTO NewAuthUserDTO { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace gswi.Model.DTOs
{
public abstract class UserDTO
{
public long Id { get; set; }
public string Name { get; set; }
public bool NotifyWhenLogin { get; set; }
}
}

View File

@ -0,0 +1,21 @@
namespace gswi.Model.Filters
{
public enum ErrorType {
ValidationError,
Exception
}
public class ErrorDto {
public ServiceErrorCode ErrorCode { get; set; }
public string Message { get; set; }
public ErrorDto(string message)
: this(ServiceErrorCode.Unknown, message) {
}
public ErrorDto(ServiceErrorCode errorCode, string message) {
Message = message;
ErrorCode = errorCode;
}
}
}

View File

@ -0,0 +1,21 @@
using System.Threading;
namespace gswi.Model.Filters
{
public enum ServiceErrorCode
{
Unknown = 0,
InvalidDependencies = 1,
InvalidData = 2,
NotFound = 3,
DataAlreadyExists = 4,
UnableToAdd = 5,
UnableToDelete = 6,
InvalidUser = 7,
ConnectionFailed = 8,
Timeout = 9,
MailError = 10
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace gswi.Model.Filters
{
public class ServiceException: Exception {
public ServiceErrorCode ErrorCode { get; set; }
public string ErrorMessage { get; set; }
public string Nameof { get; set; }
public ServiceException(ServiceErrorCode errorCode, string errorMessage) {
ErrorCode = errorCode;
ErrorMessage = errorMessage;
}
public string GetMessage() {
return ErrorMessage;
}
public string GetDetailedMessage() {
return $"{nameof(ServiceException)} - ErrorCode: {ErrorCode} - ErrorMessage: {ErrorMessage}";
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace gswi.Model {
public interface IAutoGenerateDateFields {
DateTimeOffset CreatedOn { get; set; }
DateTimeOffset LastModifiedOn { get; set; }
}
}

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="4.7.13"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using gswi.SMTP.Model;
namespace gswi.SMTP.Interface
{
public interface ISMTPClient
{
Task SendEmailAsync(EMail email);
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gswi.SMTP.Model\gswi.SMTP.Model.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

11
gswi.SMTP.Model/EMail.cs Normal file
View File

@ -0,0 +1,11 @@
using System;
namespace gswi.SMTP.Model
{
public class EMail
{
public string Receiver { get; set; }
public string Subject { get; set; }
public string Message { get; set; }
}
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,80 @@
using System;
using System.Net;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using MimeKit.Text;
using NLog;
using gswi.Configuration;
using gswi.Model.Filters;
using gswi.SMTP.Interface;
using gswi.SMTP.Model;
namespace gswi.SMTP.Service
{
public class SMTPClientImpl : ISMTPClient
{
private readonly EMailSettings _emailSettings;
private static Logger _logger = LogManager.GetCurrentClassLogger();
public SMTPClientImpl(EMailSettings emailSettings)
{
this._emailSettings = emailSettings;
}
public async Task SendEmailAsync(EMail email)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress(_emailSettings.FromName, _emailSettings.FromAddress));
emailMessage.To.Add(new MailboxAddress("", email.Receiver));
emailMessage.Subject = email.Subject;
emailMessage.Body = new TextPart(TextFormat.Html) { Text = $"{email.Message}<br><br>Dies ist eine Automatische E-Mail.<br>Gesendet von Login counter @ {System.Net.Dns.GetHostName()}" };
using (var client = new SmtpClient())
{
client.Timeout = 30000;
try
{
await client.ConnectAsync(_emailSettings.MailServerAddress, _emailSettings.MailServerPort, SecureSocketOptions.Auto).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Error(ex);
throw new ServiceException(ServiceErrorCode.ConnectionFailed, "Connection to mail server failed");
}
try
{
await client.AuthenticateAsync(new NetworkCredential(_emailSettings.Username, _emailSettings.Credentials));
}
catch (Exception ex)
{
_logger.Error(ex);
throw new ServiceException(ServiceErrorCode.InvalidUser, "Authentification to mail server failed");
}
try
{
await client.SendAsync(emailMessage).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Error(ex);
throw new ServiceException(ServiceErrorCode.MailError, "Unable to send email");
}
try
{
await client.DisconnectAsync(true).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.Error(ex);
throw new ServiceException(ServiceErrorCode.ConnectionFailed, "Unable to disconnect from mail server");
}
}
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gswi.SMTP.Model\gswi.SMTP.Model.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Interface\gswi.SMTP.Interface.csproj"/>
<ProjectReference Include="..\gswi.Configuration\gswi.Configuration.csproj"/>
<ProjectReference Include="..\gswi.Model\gswi.Model.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NETCore.MailKit" Version="2.0.3"/>
<PackageReference Include="nlog" Version="4.7.13"/>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,604 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using NLog;
using gswi.Configuration;
using gswi.Interface.Repositories;
using gswi.Interface.Services;
using gswi.Model;
using gswi.Model.DTOs;
using gswi.Model.Filters;
using gswi.Share.Common;
using gswi.SMTP.Interface;
using gswi.SMTP.Model;
namespace gswi.Service
{
public class AuthServiceImpl : IAuthService
{
private readonly IAuthUserRepository _authUserRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly AuthentificationSettings _authSettings;
private readonly ISMTPClient _smtpClient;
private readonly FrontendSettings _frontendSettings;
private static Logger _logger = LogManager.GetCurrentClassLogger();
private static Random random = new Random();
public AuthServiceImpl(
IAuthUserRepository authUserRepository,
IUnitOfWork unitOfWork,
AuthentificationSettings authSettings,
ISMTPClient smtpClient,
FrontendSettings frontendSettings
)
{
_unitOfWork = unitOfWork;
_authUserRepository = authUserRepository;
_authSettings = authSettings;
_smtpClient = smtpClient;
_frontendSettings = frontendSettings;
}
private static string _randomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
private string _generateToken(IEnumerable<Claim> claims)
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_authSettings.SecretKey));
var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
issuer: _authSettings.Issuer,
audience: _authSettings.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(_authSettings.TokenExpireTime),
signingCredentials: signingCredentials
);
return new JwtSecurityTokenHandler().WriteToken(tokenOptions);
}
private string _generateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
private async Task<string> _createAndSaveRefreshToken(AuthUser user)
{
var refreshToken = this._generateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.Now.AddDays(_authSettings.RefreshTokenExpireTime);
await _unitOfWork.SaveChangesAsync();
return refreshToken;
}
private ClaimsPrincipal _getPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._authSettings.SecretKey)),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
private async Task _createAndSaveConfirmationId(AuthUser user)
{
bool end = false;
while (!end)
{
string id = _randomString(16);
var userFromDb = await _authUserRepository.FindAuthUserByEMailConfirmationIdAsync(id);
if (userFromDb is null)
{
end = true;
user.ConfirmationId = id;
}
}
}
private async Task _createAndSaveForgotPasswordId(AuthUser user)
{
bool end = false;
while (!end)
{
string id = _randomString(16);
var userFromDb = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(id);
if (userFromDb is null)
{
end = true;
user.ForgotPasswordId = id;
}
}
}
private async Task _sendConfirmationIdToUser(AuthUser user)
{
string url = _frontendSettings.URL.EndsWith("/") ? _frontendSettings.URL : $"{_frontendSettings}/";
await _smtpClient.SendEmailAsync(new EMail()
{
Receiver = user.EMail,
Subject = $"E-Mail für {user.FirstName} {user.LastName} bestätigen",
Message = $"{url}auth/register/{user.ConfirmationId}"
});
}
private async Task _sendForgotPasswordIdToUser(AuthUser user)
{
string url = _frontendSettings.URL.EndsWith("/") ? _frontendSettings.URL : $"{_frontendSettings}/";
await _smtpClient.SendEmailAsync(new EMail()
{
Receiver = user.EMail,
Subject = $"Passwort für {user.FirstName} {user.LastName} zurücksetzen",
Message = $"{url}auth/forgot-password/{user.ForgotPasswordId}"
});
}
public async Task<List<AuthUserDTO>> GetAllAuthUsersAsync()
{
var authUserDTOs = new List<AuthUserDTO>();
var authUsers = await _authUserRepository.GetAllAuthUsersAsync();
authUsers.ForEach(authUser =>
{
authUserDTOs.Add(authUser.ToAuthUserDTO());
});
return authUserDTOs;
}
public async Task<GetFilteredAuthUsersResultDTO> GetFilteredAuthUsersAsync(AuthUserSelectCriterion selectCriterion) {
(var users, var totalCount) = await _authUserRepository.GetFilteredAuthUsersAsync(selectCriterion);
var result = new List<AuthUserDTO>();
users.ForEach(user => {
result.Add(user.ToAuthUserDTO());
});
return new GetFilteredAuthUsersResultDTO() {
Users = result,
TotalCount = totalCount
};
}
public async Task<AuthUserDTO> GetAuthUserByEMailAsync(string email)
{
try
{
var authUser = await _authUserRepository.GetAuthUserByEMailAsync(email);
return authUser.ToAuthUserDTO();
}
catch (Exception e)
{
_logger.Error(e);
throw new ServiceException(ServiceErrorCode.InvalidData, $"AuthUser with email {email} not found");
}
}
public async Task<AuthUserDTO> FindAuthUserByEMailAsync(string email)
{
var authUser = await _authUserRepository.FindAuthUserByEMailAsync(email);
return authUser != null ? authUser.ToAuthUserDTO() : null;
}
public async Task<long> AddAuthUserAsync(AuthUserDTO authUserDTO)
{
var authUserDb = await _authUserRepository.FindAuthUserByEMailAsync(authUserDTO.EMail);
if (authUserDb != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists");
}
authUserDTO.Password = ComputeHash(authUserDTO.Password, new SHA256CryptoServiceProvider());
var authUser = authUserDTO.ToAuthUser();
if (!IsValidEmail(authUser.EMail))
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail");
}
try
{
_authUserRepository.AddAuthUser(authUser);
await _createAndSaveConfirmationId(authUser);
await _sendConfirmationIdToUser(authUser);
await _unitOfWork.SaveChangesAsync();
_logger.Info($"Added authUser with email: {authUser.EMail}");
return authUser.Id;
}
catch (Exception e)
{
_logger.Error(e);
throw new ServiceException(ServiceErrorCode.UnableToAdd, $"Cannot add authUser {authUserDTO.EMail}");
}
}
public async Task<bool> ConfirmEMail(string id)
{
var user = await _authUserRepository.FindAuthUserByEMailConfirmationIdAsync(id);
if (user.ConfirmationId == id)
{
user.ConfirmationId = null;
await _unitOfWork.SaveChangesAsync();
return true;
}
return false;
}
public async Task<TokenDTO> Login(AuthUserDTO userDTO)
{
if (userDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty");
}
var userFromDb = await _authUserRepository.FindAuthUserByEMailAsync(userDTO.EMail);
if (userFromDb == null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found");
}
if (userFromDb.ConfirmationId != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed");
}
userDTO.Password = ComputeHash(userDTO.Password, new SHA256CryptoServiceProvider());
if (userFromDb.Password != userDTO.Password)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "Wrong password");
}
var tokenString = this._generateToken(new List<Claim>() {
new Claim(ClaimTypes.Name, userDTO.EMail),
new Claim(ClaimTypes.Role, userFromDb.AuthRole.ToString())
});
var refreshString = await this._createAndSaveRefreshToken(userFromDb);
if (userFromDb.ForgotPasswordId != null)
{
userFromDb.ForgotPasswordId = null;
await _unitOfWork.SaveChangesAsync();
}
return new TokenDTO
{
Token = tokenString,
RefreshToken = refreshString
};
}
public async Task ForgotPassword(string email)
{
var user = await _authUserRepository.FindAuthUserByEMailAsync(email);
if (user is null)
{
return;
}
await _createAndSaveForgotPasswordId(user);
await _sendForgotPasswordIdToUser(user);
await _unitOfWork.SaveChangesAsync();
}
public async Task<EMailStringDTO> ConfirmForgotPassword(string id)
{
var user = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(id);
return new EMailStringDTO()
{
EMail = user.EMail
};
}
public async Task ResetPassword(ResetPasswordDTO rpDTO)
{
var user = await _authUserRepository.FindAuthUserByEMailForgotPasswordIdAsync(rpDTO.Id);
if (user == null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found");
}
if (user.ConfirmationId != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed");
}
if (rpDTO.Password == null || rpDTO.Password == "")
{
throw new ServiceException(ServiceErrorCode.InvalidData, "Password is empty");
}
rpDTO.Password = ComputeHash(rpDTO.Password, new SHA256CryptoServiceProvider());
user.Password = rpDTO.Password;
await _unitOfWork.SaveChangesAsync();
}
public async Task UpdateUser(UpdateUserDTO updateUserDTO)
{
if (updateUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty");
}
if (updateUserDTO.AuthUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Existing user is empty");
}
if (updateUserDTO.NewAuthUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"New user is empty");
}
if (!IsValidEmail(updateUserDTO.AuthUserDTO.EMail) || !IsValidEmail(updateUserDTO.NewAuthUserDTO.EMail))
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail");
}
var user = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.AuthUserDTO.EMail);
if (user == null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found");
}
if (user.ConfirmationId != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed");
}
// update first name
if (updateUserDTO.NewAuthUserDTO.FirstName != null && updateUserDTO.AuthUserDTO.FirstName != updateUserDTO.NewAuthUserDTO.FirstName)
{
user.FirstName = updateUserDTO.NewAuthUserDTO.FirstName;
}
// update last name
if (updateUserDTO.NewAuthUserDTO.LastName != null && updateUserDTO.NewAuthUserDTO.LastName != "" && updateUserDTO.AuthUserDTO.LastName != updateUserDTO.NewAuthUserDTO.LastName)
{
user.LastName = updateUserDTO.NewAuthUserDTO.LastName;
}
// update E-Mail
if (updateUserDTO.NewAuthUserDTO.EMail != null && updateUserDTO.NewAuthUserDTO.EMail != "" && updateUserDTO.AuthUserDTO.EMail != updateUserDTO.NewAuthUserDTO.EMail)
{
var userByNewEMail = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.NewAuthUserDTO.EMail);
if (userByNewEMail != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists");
}
user.EMail = updateUserDTO.NewAuthUserDTO.EMail;
}
bool isExistingPasswordSet = false;
bool isnewPasswordSet = false;
// hash passwords in DTOs
if (updateUserDTO.AuthUserDTO.Password != null && updateUserDTO.AuthUserDTO.Password != "")
{
isExistingPasswordSet = true;
updateUserDTO.AuthUserDTO.Password = ComputeHash(updateUserDTO.AuthUserDTO.Password, new SHA256CryptoServiceProvider());
}
if (updateUserDTO.AuthUserDTO.Password != user.Password)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "Wrong password");
}
if (updateUserDTO.NewAuthUserDTO.Password != null && updateUserDTO.NewAuthUserDTO.Password != "")
{
isnewPasswordSet = true;
updateUserDTO.NewAuthUserDTO.Password = ComputeHash(updateUserDTO.NewAuthUserDTO.Password, new SHA256CryptoServiceProvider());
}
// update password
if (isExistingPasswordSet && isnewPasswordSet && updateUserDTO.AuthUserDTO.Password != updateUserDTO.NewAuthUserDTO.Password)
{
user.Password = updateUserDTO.NewAuthUserDTO.Password;
}
await _unitOfWork.SaveChangesAsync();
}
public async Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO)
{
if (updateUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"User is empty");
}
if (updateUserDTO.AuthUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Existing user is empty");
}
if (updateUserDTO.NewAuthUserDTO == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"New user is empty");
}
if (!IsValidEmail(updateUserDTO.AuthUserDTO.EMail) || !IsValidEmail(updateUserDTO.NewAuthUserDTO.EMail))
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Invalid E-Mail");
}
var user = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.AuthUserDTO.EMail);
if (user == null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User not found");
}
if (user.ConfirmationId != null && updateUserDTO.NewAuthUserDTO.IsConfirmed)
{
user.ConfirmationId = null;
}
else if (user.ConfirmationId == null && !updateUserDTO.NewAuthUserDTO.IsConfirmed)
{
await _createAndSaveConfirmationId(user);
}
// else
// {
// throw new ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed");
// }
// update first name
if (updateUserDTO.NewAuthUserDTO.FirstName != null && updateUserDTO.AuthUserDTO.FirstName != updateUserDTO.NewAuthUserDTO.FirstName)
{
user.FirstName = updateUserDTO.NewAuthUserDTO.FirstName;
}
// update last name
if (updateUserDTO.NewAuthUserDTO.LastName != null && updateUserDTO.NewAuthUserDTO.LastName != "" && updateUserDTO.AuthUserDTO.LastName != updateUserDTO.NewAuthUserDTO.LastName)
{
user.LastName = updateUserDTO.NewAuthUserDTO.LastName;
}
// update E-Mail
if (updateUserDTO.NewAuthUserDTO.EMail != null && updateUserDTO.NewAuthUserDTO.EMail != "" && updateUserDTO.AuthUserDTO.EMail != updateUserDTO.NewAuthUserDTO.EMail)
{
var userByNewEMail = await _authUserRepository.FindAuthUserByEMailAsync(updateUserDTO.NewAuthUserDTO.EMail);
if (userByNewEMail != null)
{
throw new ServiceException(ServiceErrorCode.InvalidUser, "User already exists");
}
user.EMail = updateUserDTO.NewAuthUserDTO.EMail;
}
// update password
if (updateUserDTO.ChangePassword && updateUserDTO.AuthUserDTO.Password != updateUserDTO.NewAuthUserDTO.Password)
{
user.Password = ComputeHash(updateUserDTO.NewAuthUserDTO.Password, new SHA256CryptoServiceProvider());
}
// update role
if (user.AuthRole == updateUserDTO.AuthUserDTO.AuthRole && user.AuthRole != updateUserDTO.NewAuthUserDTO.AuthRole)
{
user.AuthRole = updateUserDTO.NewAuthUserDTO.AuthRole;
}
await _unitOfWork.SaveChangesAsync();
}
public async Task<TokenDTO> Refresh(TokenDTO tokenDTO)
{
if (tokenDTO is null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is empty");
}
var principal = this._getPrincipalFromExpiredToken(tokenDTO.Token);
var email = principal.Identity.Name;
var user = await this._authUserRepository.FindAuthUserByEMailAsync(email);
if (user == null || user.RefreshToken != tokenDTO.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.Now)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is expired");
}
var newToken = this._generateToken(principal.Claims);
var newRefreshToken = await this._createAndSaveRefreshToken(user);
return new TokenDTO()
{
Token = newToken,
RefreshToken = newRefreshToken
};
}
public async Task Revoke(TokenDTO tokenDTO)
{
if (tokenDTO == null || tokenDTO.Token == null || tokenDTO.RefreshToken == null)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is empty");
};
var principal = this._getPrincipalFromExpiredToken(tokenDTO.Token);
var email = principal.Identity.Name;
var user = await this._authUserRepository.FindAuthUserByEMailAsync(email);
if (user == null || user.RefreshToken != tokenDTO.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.Now)
{
throw new ServiceException(ServiceErrorCode.InvalidData, $"Token is expired");
}
user.RefreshToken = null;
await _unitOfWork.SaveChangesAsync();
}
public async Task DeleteAuthUserByEMailAsync(string email)
{
try
{
await _authUserRepository.DeleteAuthUserByEMailAsync(email);
await _unitOfWork.SaveChangesAsync();
_logger.Info($"Deleted authUser with email: {email}");
}
catch (Exception e)
{
_logger.Error(e);
throw new ServiceException(ServiceErrorCode.UnableToDelete, $"Cannot delete authUser with email {email}");
}
}
public async Task DeleteAuthUserAsync(AuthUserDTO authUserDTO)
{
try
{
_authUserRepository.DeleteAuthUser(authUserDTO.ToAuthUser());
await _unitOfWork.SaveChangesAsync();
_logger.Info($"Deleted authUser {authUserDTO.EMail}");
}
catch (Exception e)
{
_logger.Error(e);
throw new ServiceException(ServiceErrorCode.UnableToDelete, $"Cannot delete authUser {authUserDTO.EMail}");
}
}
private string ComputeHash(string input, HashAlgorithm algorithm)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);
return BitConverter.ToString(hashedBytes);
}
private bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gswi.Interface\gswi.Interface.csproj"/>
<ProjectReference Include="..\gswi.Model\gswi.Model.csproj"/>
<ProjectReference Include="..\gswi.Data\gswi.Data.csproj"/>
<ProjectReference Include="..\gswi.Configuration\gswi.Configuration.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Interface\gswi.SMTP.Interface.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Model\gswi.SMTP.Model.csproj"/>
<ProjectReference Include="..\gswi.Share.Common\gswi.Share.Common.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.2"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2"/>
<PackageReference Include="NLog" Version="4.7.13"/>
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,11 @@
namespace gswi.Share.Common
{
public class AuthUserSelectCriterion : SelectCriterion
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EMail { get; set; }
public int? AuthRole { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace gswi.Share.Common
{
public class SelectCriterion
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public string SortDirection { get; set; }
public string SortColumn { get; set; }
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\gswi.Share.Common.Interface\gswi.Share.Common.Interface.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,9 @@
using System;
using Microsoft.AspNetCore.SignalR;
namespace gswi.SignalR
{
public class NotifyHub : Hub
{
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
</ItemGroup>
</Project>

127
gswi.sln Normal file
View File

@ -0,0 +1,127 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30621.155
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi", "gswi\gswi.csproj", "{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Configuration", "gswi.Configuration\gswi.Configuration.csproj", "{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.CredentialManager", "gswi.CredentialManager\gswi.CredentialManager.csproj", "{90969785-C60C-45DB-A84E-E92A65755DF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Data", "gswi.Data\gswi.Data.csproj", "{1F380FAE-D068-40BB-869A-80CE076A2C81}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Interface", "gswi.Interface\gswi.Interface.csproj", "{447C2465-462F-4DBC-95F8-2330BBE36C4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Model", "gswi.Model\gswi.Model.csproj", "{185EDC35-E150-4404-90EC-562819397D80}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Service", "gswi.Service\gswi.Service.csproj", "{F90A1288-0A41-449F-A856-F8988E77547F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Interface", "gswi.SMTP.Interface\gswi.SMTP.Interface.csproj", "{EAE98478-F76B-4A37-A428-3A4F9DF18458}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Service", "gswi.SMTP.Service\gswi.SMTP.Service.csproj", "{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SMTP.Model", "gswi.SMTP.Model\gswi.SMTP.Model.csproj", "{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.SignalR", "gswi.SignalR\gswi.SignalR.csproj", "{B55249FD-3E4B-497B-810F-C7D60DB7A09C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gswi.Share.Common", "gswi.Share.Common\gswi.Share.Common.csproj", "{53841805-BF4C-4197-98C8-79CA3609D670}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{31207121-94AB-4E14-9533-0E14E5A1D7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31207121-94AB-4E14-9533-0E14E5A1D7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31207121-94AB-4E14-9533-0E14E5A1D7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31207121-94AB-4E14-9533-0E14E5A1D7B7}.Release|Any CPU.Build.0 = Release|Any CPU
{24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24ADC76B-B4B8-4BE7-AA45-DDD70C703746}.Release|Any CPU.Build.0 = Release|Any CPU
{256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Debug|Any CPU.Build.0 = Debug|Any CPU
{256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Release|Any CPU.ActiveCfg = Release|Any CPU
{256CDBD1-03A3-45B3-AEC2-D47C8E27D297}.Release|Any CPU.Build.0 = Release|Any CPU
{1B2288C0-7376-417F-B6E7-4722674A3865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B2288C0-7376-417F-B6E7-4722674A3865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B2288C0-7376-417F-B6E7-4722674A3865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B2288C0-7376-417F-B6E7-4722674A3865}.Release|Any CPU.Build.0 = Release|Any CPU
{4EB016C1-4193-4935-9E83-C127F6B31E90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EB016C1-4193-4935-9E83-C127F6B31E90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EB016C1-4193-4935-9E83-C127F6B31E90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EB016C1-4193-4935-9E83-C127F6B31E90}.Release|Any CPU.Build.0 = Release|Any CPU
{DE15C470-100E-4D31-B81B-C28B53C244CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE15C470-100E-4D31-B81B-C28B53C244CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE15C470-100E-4D31-B81B-C28B53C244CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE15C470-100E-4D31-B81B-C28B53C244CF}.Release|Any CPU.Build.0 = Release|Any CPU
{0D798502-DF95-453D-B3D4-A49D2536A83D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D798502-DF95-453D-B3D4-A49D2536A83D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D798502-DF95-453D-B3D4-A49D2536A83D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D798502-DF95-453D-B3D4-A49D2536A83D}.Release|Any CPU.Build.0 = Release|Any CPU
{F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F62CDD28-A656-40DF-8D40-5CC8949A3A9E}.Release|Any CPU.Build.0 = Release|Any CPU
{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AB6095F-23EF-4925-9637-7F0B7DE57D4A}.Release|Any CPU.Build.0 = Release|Any CPU
{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6079B54E-42E1-4B68-8BE5-C831E6C0B3A3}.Release|Any CPU.Build.0 = Release|Any CPU
{90969785-C60C-45DB-A84E-E92A65755DF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90969785-C60C-45DB-A84E-E92A65755DF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90969785-C60C-45DB-A84E-E92A65755DF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90969785-C60C-45DB-A84E-E92A65755DF0}.Release|Any CPU.Build.0 = Release|Any CPU
{1F380FAE-D068-40BB-869A-80CE076A2C81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F380FAE-D068-40BB-869A-80CE076A2C81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F380FAE-D068-40BB-869A-80CE076A2C81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F380FAE-D068-40BB-869A-80CE076A2C81}.Release|Any CPU.Build.0 = Release|Any CPU
{447C2465-462F-4DBC-95F8-2330BBE36C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{447C2465-462F-4DBC-95F8-2330BBE36C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{447C2465-462F-4DBC-95F8-2330BBE36C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{447C2465-462F-4DBC-95F8-2330BBE36C4E}.Release|Any CPU.Build.0 = Release|Any CPU
{185EDC35-E150-4404-90EC-562819397D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{185EDC35-E150-4404-90EC-562819397D80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{185EDC35-E150-4404-90EC-562819397D80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{185EDC35-E150-4404-90EC-562819397D80}.Release|Any CPU.Build.0 = Release|Any CPU
{F90A1288-0A41-449F-A856-F8988E77547F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F90A1288-0A41-449F-A856-F8988E77547F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F90A1288-0A41-449F-A856-F8988E77547F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F90A1288-0A41-449F-A856-F8988E77547F}.Release|Any CPU.Build.0 = Release|Any CPU
{EAE98478-F76B-4A37-A428-3A4F9DF18458}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAE98478-F76B-4A37-A428-3A4F9DF18458}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAE98478-F76B-4A37-A428-3A4F9DF18458}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAE98478-F76B-4A37-A428-3A4F9DF18458}.Release|Any CPU.Build.0 = Release|Any CPU
{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC9B5EB4-EADC-4A95-8E67-4DF8B6BB8ED6}.Release|Any CPU.Build.0 = Release|Any CPU
{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7A792724-5EBF-4B12-AB8B-ACEF17F3E752}.Release|Any CPU.Build.0 = Release|Any CPU
{B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B55249FD-3E4B-497B-810F-C7D60DB7A09C}.Release|Any CPU.Build.0 = Release|Any CPU
{53841805-BF4C-4197-98C8-79CA3609D670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53841805-BF4C-4197-98C8-79CA3609D670}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53841805-BF4C-4197-98C8-79CA3609D670}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53841805-BF4C-4197-98C8-79CA3609D670}.Release|Any CPU.Build.0 = Release|Any CPU
{539E128A-E0B2-4593-A13A-9641922A50EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{539E128A-E0B2-4593-A13A-9641922A50EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{539E128A-E0B2-4593-A13A-9641922A50EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{539E128A-E0B2-4593-A13A-9641922A50EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {27320AC8-C9ED-452B-9C99-074215D41C63}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using gswi.Interface.Services;
using gswi.Model.DTOs;
using gswi.Share.Common;
namespace gswi.Controllers
{
[Route("api/auth")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(
IAuthService authServce
)
{
_authService = authServce;
}
/* Data requests */
// Get /api/auth/users
[HttpGet("users")]
[Authorize]
public async Task<List<AuthUserDTO>> GetAllAuthUsers()
{
return await _authService.GetAllAuthUsersAsync();
}
// POST /api/auth/users/get/filtered
[HttpPost("users/get/filtered")]
[Authorize]
public async Task<GetFilteredAuthUsersResultDTO> GetFilteredAuthUsers(AuthUserSelectCriterion selectCriterion)
{
return await _authService.GetFilteredAuthUsersAsync(selectCriterion);
}
// Get /api/auth/users/get/<mail>
[HttpGet("users/get/{email}")]
[Authorize]
public async Task<AuthUserDTO> GetUserFromEMail(string email)
{
return await _authService.GetAuthUserByEMailAsync(email);
}
// Get /api/auth/users/find/<mail>
[HttpGet("users/find/{email}")]
[Authorize]
public async Task<AuthUserDTO> FindUserFromEMail(string email)
{
return await _authService.FindAuthUserByEMailAsync(email);
}
/* Auth requests */
// POST /api/auth/register
[HttpPost("register")]
public async Task Register(AuthUserDTO userDTO)
{
await _authService.AddAuthUserAsync(userDTO);
}
// POST /api/auth/register/<id>
[HttpPost("register/{id}")]
public async Task<bool> ConfirmEMail(string id)
{
return await _authService.ConfirmEMail(id);
}
// POST /api/auth/login
[HttpPost("login")]
public async Task<TokenDTO> Login(AuthUserDTO userDTO)
{
return await _authService.Login(userDTO);
}
// POST /api/auth/forgot-password
[HttpPost("forgot-password")]
public async Task ForgotPassword([FromBody] string email)
{
await _authService.ForgotPassword(email);
}
// POST /api/auth/confirm-forgot-password
[HttpPost("confirm-forgot-password")]
public async Task<EMailStringDTO> ConfirmForgotPassword([FromBody] string id)
{
return await _authService.ConfirmForgotPassword(id);
}
// POST /api/auth/reset-password
[HttpPost("reset-password")]
public async Task ResetPassword(ResetPasswordDTO rpDTO)
{
await _authService.ResetPassword(rpDTO);
}
// POST /api/auth/update-user
[HttpPost("update-user")]
public async Task UpdateUser(UpdateUserDTO updateUserDTO)
{
await _authService.UpdateUser(updateUserDTO);
}
// POST /api/auth/update-user-as-admin
[HttpPost("update-user-as-admin")]
[Authorize]
public async Task UpdateUserAsAdmin(AdminUpdateUserDTO updateUserDTO)
{
await _authService.UpdateUserAsAdmin(updateUserDTO);
}
// POST /api/auth/refresh
[HttpPost("refresh")]
public async Task<TokenDTO> Refresh(TokenDTO tokenDTO)
{
return await _authService.Refresh(tokenDTO);
}
// POST /api/auth/revoke
[HttpPost("revoke")]
public async Task Revoke(TokenDTO tokenDTO)
{
await _authService.Revoke(tokenDTO);
}
// POST /api/auth/delete-user
[HttpPost("delete-user")]
public async Task DeleteAuthUserAsync(AuthUserDTO userDTO)
{
await _authService.DeleteAuthUserAsync(userDTO);
}
// POST /api/auth/delete-user
[HttpPost("delete-user-by-mail/{mail}")]
public async Task DeleteAuthUserByEMailAsync(string mail)
{
await _authService.DeleteAuthUserByEMailAsync(mail);
}
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using gswi.Configuration;
using gswi.Model.DTOs;
using gswi.SMTP.Interface;
using gswi.SMTP.Model;
namespace gswi.Controllers
{
[Route("api/gui")]
[ApiController]
public class GUIController : ControllerBase
{
private APISettings _apiSettings;
private DatabaseSettings _databaseSettings;
private AuthentificationSettings _authSettings;
private EMailSettings _mailSettings;
private FrontendSettings _frontendSettings;
private IHostEnvironment _env;
private readonly ISMTPClient _smtpClient;
public GUIController(
APISettings apiSettings,
DatabaseSettings databaseSettings,
AuthentificationSettings authSettings,
EMailSettings mailSettings,
FrontendSettings frontendSettings,
IHostEnvironment env,
ISMTPClient smtpClient
)
{
this._apiSettings = apiSettings;
this._databaseSettings = databaseSettings;
this._authSettings = authSettings;
this._mailSettings = mailSettings;
this._frontendSettings = frontendSettings;
this._env = env;
this._smtpClient = smtpClient;
}
// GET /api/gui/api-version
[HttpGet("api-version")]
public ApiVersionDTO GetApiVersion()
{
return new ApiVersionDTO()
{
Major = this._apiSettings.ApiVersion.Major,
Minor = this._apiSettings.ApiVersion.Minor,
Micro = this._apiSettings.ApiVersion.Micro
};
}
// GET /api/gui/settings
[HttpGet("settings")]
[Authorize]
public SettingsDTO GetSettingsDTO()
{
return new SettingsDTO()
{
ApiVersion = this._apiSettings.ApiVersion.ToString(),
ConfigPath = this._env.ContentRootPath,
WebBaseURL = this._frontendSettings.URL,
ApiBaseURL = "",
TokenExpireTime = this._authSettings.TokenExpireTime,
RefreshTokenExpireTime = this._authSettings.RefreshTokenExpireTime,
MailUser = this._mailSettings.Username,
MailPort = this._mailSettings.MailServerPort,
MailHost = this._mailSettings.MailServerAddress,
MailTransceiver = this._mailSettings.FromName,
MailTransceiverAddress = this._mailSettings.FromAddress
};
}
// POST /api/gui/send-test-mail/<email>
[HttpPost("send-test-mail/{email}")]
[Authorize]
public async Task SendTestMail(string email)
{
await _smtpClient.SendEmailAsync(new EMail()
{
Receiver = email,
Subject = $"Login counter Test E-Mail",
Message = $"Login counter Test E-Mail"
});
}
}
}

76
gswi/DataSeeder.cs Normal file
View File

@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Microsoft.EntityFrameworkCore;
using gswi.Data;
using gswi.Model;
namespace gswi
{
public class DataSeeder
{
private readonly DatabaseContext _databaseContext;
public DataSeeder(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public void SeedData()
{
_databaseContext.Database.EnsureCreated();
if (!_databaseContext.AuthUsers.Any())
{
var admin = new AuthUser()
{
FirstName = "Admin",
LastName = "Administator",
EMail = "admin@localhost",
Password = ComputeHash("Administator", new SHA256CryptoServiceProvider()),
AuthRole = AuthRoles.Admin
};
var authUser = new AuthUser()
{
FirstName = "Max",
LastName = "Mustermann",
EMail = "max.mustermann@gmail.com",
Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()),
};
var authUser1 = new AuthUser()
{
FirstName = "Max",
LastName = "Tester",
EMail = "max.mustermann@mustermail.com",
Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()),
};
var authUser2 = new AuthUser()
{
FirstName = "Max",
LastName = "Muster",
EMail = "max.mustermann@yahoo.com",
Password = ComputeHash("test1234", new SHA256CryptoServiceProvider()),
};
_databaseContext.AuthUsers.Add(admin);
_databaseContext.AuthUsers.Add(authUser);
_databaseContext.AuthUsers.Add(authUser1);
_databaseContext.AuthUsers.Add(authUser2);
}
_databaseContext.SaveChanges();
}
public string ComputeHash(string input, HashAlgorithm algorithm)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);
return BitConverter.ToString(hashedBytes);
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using NLog;
using gswi.Model.Filters;
namespace gswi.Filters
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private static Logger _logger = LogManager.GetCurrentClassLogger();
public CustomExceptionFilterAttribute() { }
public override void OnException(ExceptionContext context)
{
context.Result = CreateJsonErrorResult(context);
base.OnException(context);
}
public JsonResult CreateJsonErrorResult(ExceptionContext context)
{
JsonResult result = null;
// create jsonresult
var exception = context.Exception;
if (exception is ServiceException)
{
var bex = exception as ServiceException;
_logger.Error($"{bex.GetDetailedMessage()}");
var error = new ErrorDto(bex.ErrorCode, bex.GetMessage());
result = new JsonResult(error)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
else
{
var trackingId = Guid.NewGuid();
string userMsg = $"Tracking Id: {trackingId}";
_logger.Error(exception, userMsg);
var error = new ErrorDto($"Tracking Id: {trackingId}");
result = new JsonResult(error)
{
StatusCode = (int)HttpStatusCode.InternalServerError
};
}
return result;
}
}
}

30
gswi/HostExtensions.cs Normal file
View File

@ -0,0 +1,30 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using gswi.Data;
namespace gswi
{
public static class HostExtensions
{
public static IHost SeedData(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetService<DatabaseContext>();
// now we have the DbContext. Run migrations
context.Database.Migrate();
#if DEBUG
// now that the database is up to date. Let's seed
new DataSeeder(context).SeedData();
#endif
}
return host;
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
namespace gswi.JSONConverter
{
public abstract class JsonCreationConverter<T> : JsonConverter
{
static Logger _logger = LogManager.GetCurrentClassLogger();
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
try
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
catch (Exception e)
{
_logger.Error(e);
return null;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
}

43
gswi/Program.cs Normal file
View File

@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using gswi.Configuration;
using System;
namespace gswi
{
public class Program
{
static void Main(string[] args)
{
try
{
Configuration.HostExtensions.ChangeCurrentDirToProjectDir();
var host = CreateHostBuilder(args).Build();
host.LogHostingEnvironment<Program>();
host.SeedData();
host.Run();
}
catch (Exception ex)
{
Configuration.HostExtensions.LogStartError(ex);
Console.WriteLine(ex.ToString());
}
finally
{
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).ConfigureLogging((hostCtx, loggingBuilder) =>
{
loggingBuilder.SetMinimumLevel(LogLevel.Trace);
loggingBuilder.AddNLog($"nlog-{hostCtx.HostingEnvironment.EnvironmentName}.config");
});
}
}

View File

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50843",
"sslPort": 44360
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"login_counter": {
"commandName": "Project",
"launchUrl": "weatherforecast",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

186
gswi/Startup.cs Normal file
View File

@ -0,0 +1,186 @@
using System.Text;
using System;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using NLog.Extensions.Logging;
using NLog;
using gswi.Configuration;
using gswi.CredentialManager;
using gswi.Data;
using gswi.Data.Repositories;
using gswi.Filters;
using gswi.Interface.Repositories;
using gswi.Interface.Services;
using gswi.Service;
using gswi.SMTP.Interface;
using gswi.SMTP.Service;
using gswi.SignalR;
using Microsoft.AspNetCore.HttpOverrides;
namespace gswi
{
public class Startup
{
public IConfiguration Configuration { get; }
private static Logger _logger = LogManager.GetCurrentClassLogger();
public Startup(IHostEnvironment env)
{
try
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (string.IsNullOrEmpty(environmentName))
environmentName = "production";
var builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddJsonFile($"appsettings.{System.Net.Dns.GetHostName()}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
catch (Exception e)
{
_logger.Error(e);
}
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddConfiguration(Configuration.GetSection("Logging"));
builder.AddNLog();
builder.AddConsole();
});
var frontend = GetTypedSettingsFromSection<FrontendSettings>("Frontend");
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder.WithOrigins(frontend.URL)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed((host) => true));
});
var dbSettings = GetTypedSettingsFromSection<DatabaseSettings>("Database");
var authSettings = GetTypedSettingsFromSection<AuthentificationSettings>("Authentification");
var emailSettings = GetTypedSettingsFromSection<EMailSettings>("EMail");
var apiSettings = GetTypedSettingsFromSection<APISettings>("API");
emailSettings.Credentials = Base64.Decode(emailSettings.Credentials);
services.AddSingleton(dbSettings);
services.AddSingleton(authSettings);
services.AddSingleton(emailSettings);
services.AddSingleton(apiSettings);
services.AddSingleton(GetTypedSettingsFromSection<FrontendSettings>("Frontend"));
services.AddDbContextPool<DatabaseContext>(opt => opt.UseMySql(
Base64.DecodeConnectionString(dbSettings.ConnectionString, dbSettings.Credentials),
new MySqlServerVersion(new Version(10, 3, 32))
));
services.AddTransient<IAuthService, AuthServiceImpl>();
services.AddTransient<ISMTPClient, SMTPClientImpl>();
services.AddScoped<IUnitOfWork>((s) => s.GetRequiredService<DatabaseContext>());
services.AddScoped<IAuthUserRepository>((s) => new AuthUserRepositoryImpl((DatabaseContext)s.GetService<IUnitOfWork>()));
services.AddSignalR();
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(opt =>
{
opt.RequireHttpsMetadata = false;
opt.SaveToken = true;
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = authSettings.Issuer,
ValidAudience = authSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey))
};
});
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
// options.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
});
services.AddControllers(options =>
{
options.Filters.Add(typeof(CustomExceptionFilterAttribute));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder gswi, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
gswi.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
if (env.IsDevelopment())
{
gswi.UseDeveloperExceptionPage();
}
NLog.LogManager.LoadConfiguration($"nlog-{env.EnvironmentName}.config");
gswi.UseCors("CorsPolicy");
var apiSettings = GetTypedSettingsFromSection<APISettings>("API");
if (apiSettings.RedirectToHTTPS)
{
gswi.UseHttpsRedirection();
}
gswi.UseRouting();
gswi.UseAuthentication();
gswi.UseAuthorization();
gswi.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<NotifyHub>("/notify");
});
}
protected SettingType GetTypedSettingsFromSection<SettingType>(string sectionName) where SettingType : new()
{
try
{
var settings = new SettingType();
Configuration.GetSection(sectionName).Bind(settings);
return settings;
}
catch (Exception e)
{
_logger.Error(e);
return new SettingType();
}
}
}
}

18
gswi/appsettings.json Normal file
View File

@ -0,0 +1,18 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"API": {
"RedirectToHTTPS": true,
"ApiVersion": {
"Major": "HEAD",
"Minor": "0",
"Micro": "0"
}
},
"AllowedHosts": "*"
}

43
gswi/gswi.csproj Normal file
View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>gswi</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="nlog-production.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="nlog-staging.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="nlog-development.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="BeforeBuildEvent" BeforeTargets="BeforeBuild">
<Exec Command="powershell -executionpolicy remotesigned -File ./update-version.ps1" Condition="'$(OS)' == 'Windows_NT'"/>
<Exec Command="pwsh -executionpolicy remotesigned -File ./update-version.ps1" Condition="$([MSBuild]::IsOSPlatform('Linux'))"/>
</Target>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.2"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.2"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.2"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0"/>
<PackageReference Include="NLog" Version="4.7.13"/>
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gswi.Service\gswi.Service.csproj"/>
<ProjectReference Include="..\gswi.Data\gswi.Data.csproj"/>
<ProjectReference Include="..\gswi.Configuration\gswi.Configuration.csproj"/>
<ProjectReference Include="..\gswi.Model\gswi.Model.csproj"/>
<ProjectReference Include="..\gswi.Interface\gswi.Interface.csproj"/>
<ProjectReference Include="..\gswi.CredentialManager\gswi.CredentialManager.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Model\gswi.SMTP.Model.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Interface\gswi.SMTP.Interface.csproj"/>
<ProjectReference Include="..\gswi.SMTP.Service\gswi.SMTP.Service.csproj"/>
<ProjectReference Include="..\gswi.SignalR\gswi.SignalR.csproj"/>
<ProjectReference Include="..\gswi.Share.Common\gswi.Share.Common.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="logs/internal-nlog.txt">
<targets>
<target name="login_counter-specific-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/${logger}/${logger}-lc.{##}.txt" fileName="logs/${logger}/${logger}-lc.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
<target name="common-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/login-counter.{##}.txt" fileName="logs/login-counter.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
</targets>
<rules>
<logger name="LC-*" minlevel="Warn" writeTo="common-file" />
<logger name="LC-*" minlevel="Info" writeTo="gateway-specific-file" final="true"/>
<logger name="*" minLevel="Trace" writeTo="common-file"/>
</rules>
</nlog>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="logs/internal-nlog.txt">
<targets>
<target name="login_counter-specific-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/${logger}/${logger}-lc.{##}.txt" fileName="logs/${logger}/${logger}-lc.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
<target name="common-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/login-counter.{##}.txt" fileName="logs/login-counter.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
</targets>
<rules>
<logger name="LC-*" minlevel="Warn" writeTo="common-file" />
<logger name="LC-*" minlevel="Trace" writeTo="gateway-specific-file" final="true"/>
<logger name="*" minLevel="Trace" writeTo="common-file"/>
</rules>
</nlog>

26
gswi/nlog-staging.config Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="logs/internal-nlog.txt">
<targets>
<target name="login_counter-specific-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/${logger}/${logger}-lc.{##}.txt" fileName="logs/${logger}/${logger}-lc.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
<target name="common-file" xsi:type="AsyncWrapper" overflowAction="Grow" >
<target xsi:type="File" archiveFileName="logs/login-counter.{##}.txt" fileName="logs/login-counter.txt"
layout="${longdate} [${threadid}] ${level:uppercase=true} ${logger} - ${message} ${exception:format=ToString}"
maxArchiveFiles="10" archiveNumbering="Rolling" archiveEvery="Day" />
</target>
</targets>
<rules>
<logger name="LC-*" minlevel="Warn" writeTo="common-file" />
<logger name="LC-*" minlevel="Trace" writeTo="gateway-specific-file" final="true"/>
<logger name="*" minLevel="Trace" writeTo="common-file"/>
</rules>
</nlog>

52
gswi/update-version.ps1 Normal file
View File

@ -0,0 +1,52 @@
#! /snap/bin/pwsh
function Get-VersionFromBranch {
$branch = git rev-parse --abbrev-ref HEAD
$versions = $branch.Split('.')
$major = "0"
$minor = "0"
$micro = "0"
if ($versions.Count -gt 0) {
$major = $versions[0]
}
if ($versions.Count -gt 1) {
$minor = $versions[1]
}
if ($versions.Count -gt 2) {
$micro = $versions[2]
}
return $major, $minor, $micro
}
function Get-VersionAsJSON([string]$major, [string]$minor, [string]$micro) {
return @{
"Major" = "$major"
"Minor" = "$minor"
"Micro" = "$micro"
} | ConvertTo-Json
}
function Get-AppsettingsAsJSON() {
return Get-Content -Raw -Path "./appsettings.json" | ConvertFrom-Json
}
function Set-AppsettingsAsJSON($settings) {
$settings | ConvertTo-Json | Out-File "./appsettings.json"
}
function Set-VersionFromBranch([string]$major, [string]$minor, [string]$micro) {
$versionJSON = Get-VersionAsJSON $major $minor $micro
$appsettings = Get-AppsettingsAsJSON
$appsettings.API.ApiVersion.Major = $major
$appsettings.API.ApiVersion.Minor = $minor
$appsettings.API.ApiVersion.Micro = $micro
Set-AppsettingsAsJSON $appsettings
}
$res = Get-VersionFromBranch
Set-VersionFromBranch $res[0] $res[1] $res[2]