A new start

This commit is contained in:
Laura Hausmann 2023-06-01 06:14:20 +02:00
commit ec4ed6f0be
Signed by: zotan
GPG key ID: D044E84C5BE01605
100 changed files with 20029 additions and 0 deletions

189
.gitignore vendored Normal file
View file

@ -0,0 +1,189 @@
# ---> dotnet-jetbrains-rider-macos
/packages/
/_ReSharper.Caches/
# Created by https://www.toptal.com/developers/gitignore/api/rider,dotnetcore,jetbrains+all
# Edit at https://www.toptal.com/developers/gitignore?templates=rider,dotnetcore,jetbrains+all
### DotnetCore ###
# .NET Core build folders
bin/
obj/
# Common node modules locations
/node_modules
/wwwroot/node_modules
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Rider ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
# Generated files
# Sensitive or high-churn files
# Gradle
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
# Mongo Explorer plugin
# File-based project format
# IntelliJ
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Cursive Clojure plugin
# Crashlytics plugin (for Android Studio and IntelliJ)
# Editor-based Rest Client
# Android studio 3.1+ serialized cache file
# End of https://www.toptal.com/developers/gitignore/api/rider,dotnetcore,jetbrains+all
# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.toptal.com/developers/gitignore/api/macos
database.db
database.db-wal
database.db-shm

27
Authinator.csproj Normal file
View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="1.1.12" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
</ItemGroup>
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
<Exec Command="git describe --long --always --dirty --exclude=* --abbrev=8 2&gt;/dev/null || echo unknown" ConsoleToMSBuild="True" IgnoreExitCode="False">
<Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput" />
</Exec>
</Target>
</Project>

16
Authinator.sln Normal file
View file

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authinator", "Authinator.csproj", "{6ED539A5-17F5-4C59-A85F-7DC427D8A9ED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6ED539A5-17F5-4C59-A85F-7DC427D8A9ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6ED539A5-17F5-4C59-A85F-7DC427D8A9ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6ED539A5-17F5-4C59-A85F-7DC427D8A9ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6ED539A5-17F5-4C59-A85F-7DC427D8A9ED}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,57 @@
using System.Reflection;
using System.Web;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.EntityFrameworkCore;
namespace Authinator.Backend.Database;
public class DatabaseContext : DbContext {
public DatabaseContext() { }
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public virtual DbSet<Network> Networks { get; set; } = null!;
public virtual DbSet<Config> Config { get; set; } = null!;
public virtual DbSet<Group> Groups { get; set; } = null!;
public virtual DbSet<User> Users { get; set; } = null!;
public virtual DbSet<ACL> ACLs { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlite("Data Source=database.db");
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
public async void Initialize() {
Config.Set("cookie_name", "authinator-session", false);
Config.Set("admin_group", "admin", false);
Config.Set("hmac_secret", CryptoUtils.GetRandomBytesBase64(64), false);
Config.Set("auth_token_lifetime", "31556926", false); // default auth token lifetime is 1 year
Config.Set("pw_reset_token_lifetime", "3600", false); // default pw reset token lifetime is 1 hour
await SaveChangesAsync();
Config.UpdateCache();
if (!Groups.Any(p => p.Name == ConfigCache.AdminGroup)) {
Groups.Add(new Group { Name = ConfigCache.AdminGroup });
await SaveChangesAsync();
}
if (!Users.Include(p => p.Groups).Any(p => p.Groups.Any(q => q.Name == ConfigCache.AdminGroup))) {
Console.WriteLine($"* No admin user found, creating one...");
var user = new User { Reference = "admin_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(), Groups = new List<Group> { Groups.First(p => p.Name == ConfigCache.AdminGroup) } };
await Users.AddAsync(user);
await SaveChangesAsync();
Console.WriteLine($"* User '{user.Reference}' created, activate it on /FinishRegistration using this token: {user.GetSignupToken()}");
}
else if (!Users.Include(p => p.Groups).AsEnumerable().Any(p => p.Groups.Any(q => q.Name == ConfigCache.AdminGroup) && p.IsSignupComplete)) {
var user = Users.Include(p => p.Groups).First(p => p.Groups.Any(q => q.Name == ConfigCache.AdminGroup));
Console.WriteLine($"* Admin user not activated yet, activate it on /FinishRegistration using this token: {user.GetSignupToken()}");
}
else {
Globals.NoActiveAdminUser = false;
}
}
}

View file

@ -0,0 +1,30 @@
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public record ACL {
public int Id { get; set; }
public string Name { get; set; } = null!;
public bool Enabled { get; set; } = true;
public string Target { get; set; } = null!;
public bool TargetIsRegex { get; set; }
public List<User> Users { get; set; } = new();
public List<Group> Groups { get; set; } = new();
public List<Network> Networks { get; set; } = new();
public class Configuration : IEntityTypeConfiguration<ACL> {
public void Configure(EntityTypeBuilder<ACL> acl) {
acl.ToTable("ACLs");
acl.HasKey(p => p.Id);
acl.Property(b => b.Name).HasColumnName("Name").IsRequired();
acl.Property(p => p.Target).HasColumnName("Target").IsRequired();
acl.Property(p => p.Enabled).HasColumnName("Enabled").IsRequired();
acl.Property(p => p.TargetIsRegex).HasColumnName("TargetIsRegex").HasDefaultValue(false);
acl.HasMany(p => p.Networks).WithMany();
acl.HasMany(p => p.Groups).WithMany();
acl.HasMany(p => p.Users).WithMany();
}
}
}

View file

@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public record Config(string Key, string Value) {
public string Key { get; set; } = Key;
public string Value { get; set; } = Value;
public class Configuration : IEntityTypeConfiguration<Config> {
public void Configure(EntityTypeBuilder<Config> config) {
config.ToTable("Config");
config.HasKey(p => p.Key);
config.Property(b => b.Key).HasColumnName("Key").IsRequired();
config.Property(b => b.Value).HasColumnName("Value").IsRequired();
}
}
}
public static class ConfigUtils {
public static bool HasKey(this DbSet<Config> config, string key) {
return config.Any(p => p.Key == key);
}
public static string? Get(this DbSet<Config> config, string key) {
return config.HasKey(key) ? config.First(p => p.Key == key).Value : null;
}
public static async void Set(this DbSet<Config> config, string key, string value, bool overwrite = true) {
if (!config.HasKey(key))
await config.AddAsync(new Config(key, value));
else if (overwrite)
config.First(p => p.Key == key).Value = value;
}
}
public static class ConfigCache {
public static string CookieName = null!;
public static string AdminGroup = null!;
public static string HmacSecret = null!;
public static TimeSpan AuthTokenLifetime;
public static TimeSpan PwResetTokenLifetime;
public static void UpdateCache(this DbSet<Config> config) {
CookieName = config.Get("cookie_name")!;
AdminGroup = config.Get("admin_group")!;
HmacSecret = config.Get("hmac_secret")!;
AuthTokenLifetime = TimeSpan.FromSeconds(long.Parse(config.Get("auth_token_lifetime")!));
PwResetTokenLifetime = TimeSpan.FromSeconds(long.Parse(config.Get("pw_reset_token_lifetime")!));
}
}

View file

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public record Group {
public int Id { get; set; }
public string Name { get; set; } = null!;
public class Configuration : IEntityTypeConfiguration<Group> {
public void Configure(EntityTypeBuilder<Group> group) {
group.ToTable("Groups");
group.HasKey(p => p.Id);
group.Property(b => b.Name).HasColumnName("Name").IsRequired();
}
}
}

View file

@ -0,0 +1,20 @@
using System.Net;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public record Network {
public int Id { get; set; }
public string Name { get; set; } = null!;
public IPAddress Subnet { get; set; } = null!;
public class Configuration : IEntityTypeConfiguration<Network> {
public void Configure(EntityTypeBuilder<Network> group) {
group.ToTable("Networks");
group.HasKey(p => p.Id);
group.Property(b => b.Name).HasColumnName("Name").IsRequired();
group.Property(b => b.Name).HasColumnName("Subnet").IsRequired();
}
}
}

View file

@ -0,0 +1,113 @@
using System.Diagnostics.CodeAnalysis;
using System.Web;
using Authinator.Backend.Utils;
using Isopoh.Cryptography.Argon2;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Authinator.Backend.Database.Tables;
public class User {
public int Id { get; set; }
public int Iteration { get; set; }
public string Reference { get; set; } = null!;
public string? Username { get; set; }
public string? Password { get; set; }
public string? Email { get; set; }
public List<Group> Groups { get; set; } = new();
public bool IsSignupComplete => !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password) && !string.IsNullOrEmpty(Email);
public string GetAuthToken() => $"auth:{Id}:{Iteration}:{GetAuthTokenExpiry()}:".HmacWithMessage(ConfigCache.HmacSecret);
private static long GetAuthTokenExpiry() => DateTimeOffset.UtcNow.Add(ConfigCache.AuthTokenLifetime).ToUnixTimeSeconds();
public string GetSignupToken() => $"register:{Id}:".HmacWithMessage(ConfigCache.HmacSecret);
public string GetPwResetToken() => $"reset:{Id}:{Iteration}:{GetPwResetTokenExpiry()}:".HmacWithMessage(ConfigCache.HmacSecret);
private static long GetPwResetTokenExpiry() => DateTimeOffset.UtcNow.Add(ConfigCache.PwResetTokenLifetime).ToUnixTimeSeconds();
[SuppressMessage("ReSharper.DPA", "DPA0001: Memory allocation issues")]
public bool ValidatePassword(string password) => Argon2.Verify(Password ?? string.Empty, password);
[SuppressMessage("ReSharper.DPA", "DPA0001: Memory allocation issues")]
public void SetPassword(string password) => Password = Argon2.Hash(password);
public class Configuration : IEntityTypeConfiguration<User> {
public void Configure(EntityTypeBuilder<User> user) {
user.ToTable("Users");
user.HasKey(p => p.Id);
user.Property(b => b.Iteration).HasColumnName("Iteration").HasDefaultValue(0);
user.Property(b => b.Reference).HasColumnName("Reference").IsRequired();
user.Property(b => b.Username).HasColumnName("Username");
user.Property(b => b.Password).HasColumnName("Password");
user.Property(b => b.Email).HasColumnName("Email");
user.HasMany(p => p.Groups).WithMany();
}
}
private bool Equals(User other) => Id == other.Id;
public override bool Equals(object? obj) {
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return obj.GetType() == GetType() && Equals((User)obj);
}
public override int GetHashCode() => Id;
public static bool operator ==(User? left, User? right) => Equals(left, right);
public static bool operator !=(User? left, User? right) => !Equals(left, right);
}
public static class UserUtils {
public static User? ValidateAuthToken(this DbSet<User> users, string token) {
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("auth:") || token.Split(":").Length != 5)
return null;
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return null;
var expiry = long.Parse(message.Split(":")[3]);
if (DateTimeOffset.FromUnixTimeSeconds(expiry) < DateTimeOffset.UtcNow)
return null;
var userId = int.Parse(message.Split(":")[1]);
var userIteration = int.Parse(message.Split(":")[2]);
return users.Include(p => p.Groups).FirstOrDefault(p => p.Id == userId && p.Iteration == userIteration);
}
public static bool ValidateResetToken(this User user, string token) {
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("reset:") || token.Split(":").Length != 5)
return false;
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return false;
var expiry = long.Parse(message.Split(":")[3]);
if (DateTimeOffset.FromUnixTimeSeconds(expiry) < DateTimeOffset.UtcNow)
return false;
var userId = int.Parse(message.Split(":")[1]);
var userIteration = int.Parse(message.Split(":")[2]);
return user.Id == userId && user.Iteration == userIteration;
}
}

45
Backend/Startup.cs Normal file
View file

@ -0,0 +1,45 @@
using System.Reflection;
using Authinator.Backend.Database;
using Authinator.Backend.Utils;
using Microsoft.EntityFrameworkCore;
using Swashbuckle.AspNetCore.Filters;
var db = new DatabaseContext();
db.Database.Migrate();
db.Initialize();
var builder = WebApplication.CreateBuilder(args);
if (Environment.GetCommandLineArgs().Contains("--razor-runtime-comp")) {
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
builder.Services.AddControllers().AddRazorRuntimeCompilation();
}
else {
builder.Services.AddRazorPages();
builder.Services.AddControllers();
}
builder.Services.AddSwaggerGen(options => {
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
options.ExampleFilters();
});
builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());
var app = builder.Build();
if (!app.Environment.IsDevelopment())
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error");
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();

View file

@ -0,0 +1,16 @@
using System.Reflection;
namespace Authinator.Backend.Utils;
public static class ApplicationInformation {
public const bool WideContainers = false;
public const string Project = "Authinator";
public const string Repository = "https://git.ztn.sh/zotan/Authinator";
public static readonly string Version =
((AssemblyInformationalVersionAttribute)Assembly.GetEntryAssembly()!.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)[0])
.InformationalVersion[6..];
public static readonly string LinkVersion = Version.Length >= 8 ? Version[..8] : Version;
public static readonly bool HasVersion = Version != "unknown";
}

View file

@ -0,0 +1,38 @@
using System.Security.Cryptography;
using System.Text;
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Isopoh.Cryptography.Argon2;
namespace Authinator.Backend.Utils;
public static class AuthUtils {
public const string OverrideAuthCli = "--disable-auth";
private const string InternalUserPrefix = "_";
private const string GuestUser = $"{InternalUserPrefix}guest";
private const string FallbackUser = $"{InternalUserPrefix}debug";
private static readonly string[] FallbackGroups = { "admin" };
public static readonly bool OverrideAuth = Environment.GetCommandLineArgs().Contains(OverrideAuthCli);
public static string GetRemoteUsername(this HttpContext ctx, DatabaseContext db)
=> (OverrideAuth ? FallbackUser : db.Users.ValidateAuthToken(ctx.Request.Cookies[ConfigCache.CookieName]!)?.Username) ?? GuestUser;
public static User? GetRemoteUser(this HttpContext ctx, DatabaseContext db) => db.Users.ValidateAuthToken(ctx.Request.Cookies[ConfigCache.CookieName]!);
public static bool IsAuthenticated(this HttpContext ctx, DatabaseContext db)
=> OverrideAuth || (ctx.Request.Cookies.ContainsKey(ConfigCache.CookieName) && db.Users.ValidateAuthToken(ctx.Request.Cookies[ConfigCache.CookieName]!) != null);
public static bool IsAdmin(this HttpContext ctx, DatabaseContext db) => OverrideAuth
|| (ctx.Request.Cookies.ContainsKey(ConfigCache.CookieName)
&& ctx.GetRemoteUser(db) != null
&& ctx.GetRemoteUser(db)!.Groups.Any(p => p.Name == ConfigCache.AdminGroup));
public static IEnumerable<string> GetRemoteGroups(this HttpContext ctx) {
if (OverrideAuth)
return FallbackGroups;
return ctx.Request.Headers.TryGetValue("Remote-Groups", out var header) ? header.ToString().Split(",") : Array.Empty<string>();
}
}

View file

@ -0,0 +1,35 @@
using System.Globalization;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Authinator.Backend.Utils;
public static class CookieUtils {
public static void AppendUnencodedCookie(this HttpResponse response, string key, string value) {
response.Cookies.Delete(key);
var setCookieHeaderValue = new SetCookieHeaderValue(key, value.Replace(" ", "+"));
response.Headers[HeaderNames.SetCookie] = StringValues.Concat(response.Headers[HeaderNames.SetCookie], setCookieHeaderValue.ToString());
}
public static void AppendUnencodedCookie(this HttpResponse response, string key, string value, CookieOptions options) {
if (options == null) {
throw new ArgumentNullException(nameof(options));
}
response.Cookies.Delete(key);
var setCookieHeaderValue = new SetCookieHeaderValue(key, value.Replace(" ", "+")) {
Domain = options.Domain,
Path = options.Path,
Expires = options.Expires,
MaxAge = options.MaxAge,
Secure = options.Secure,
SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)options.SameSite,
HttpOnly = options.HttpOnly
};
response.Headers[HeaderNames.SetCookie] = StringValues.Concat(response.Headers[HeaderNames.SetCookie], setCookieHeaderValue.ToString());
}
}

View file

@ -0,0 +1,30 @@
using System.Security.Cryptography;
using System.Text;
namespace Authinator.Backend.Utils;
public static class CryptoUtils {
public static string Hmac(this string text, string key) {
using var hmac = new HMACSHA256(ConvertFromBase64WithUtf8Fallback(key));
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(text)));
}
public static string HmacWithMessage(this string text, string key) => text + text.Hmac(key);
public static byte[] GetRandomBytes(int size) {
var random = new Random(Guid.NewGuid().GetHashCode());
var buffer = new byte[size];
random.NextBytes(buffer);
return buffer;
}
public static string GetRandomBytesBase64(int size) => Convert.ToBase64String(GetRandomBytes(size));
public static string FixUrlEncodedBase64(this string str) => str.Replace("%2F", "/").Replace("%2f", "/");
public static byte[] ConvertFromBase64WithUtf8Fallback(string text) {
var buffer = new Span<byte>(new byte[text.Length]);
var success = Convert.TryFromBase64String(text, buffer, out var bytesParsed);
return success ? buffer[..bytesParsed].ToArray() : Encoding.UTF8.GetBytes(text);
}
}

5
Backend/Utils/Globals.cs Normal file
View file

@ -0,0 +1,5 @@
namespace Authinator.Backend.Utils;
public static class Globals {
public static bool NoActiveAdminUser = true;
}

View file

@ -0,0 +1,55 @@
using System.Net;
using System.Text.RegularExpressions;
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Authinator.Controllers;
[Controller]
[SkipStatusCodePages]
[Route("/api/authenticate")]
public class AuthenticateController : Controller {
[HttpGet]
public IActionResult Get() {
var db = new DatabaseContext();
if (!HttpContext.IsAuthenticated(db))
return Unauthorized();
var user = HttpContext.GetRemoteUser(db);
if (user == null) {
return Ok();
}
var srcAddress = Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',').FirstOrDefault();
var domain = Request.Host;
var acls = db.ACLs.Include(p => p.Groups).Include(p => p.Users).AsEnumerable();
var aclsThatApplyRegex = acls.Where(p => p.TargetIsRegex).Where(p => new Regex(p.Target).IsMatch(domain.ToString()));
var aclsThatApplyMatch = acls.Where(p => !p.TargetIsRegex)
.Where(p => p.Target.StartsWith("*") ? domain.ToString().EndsWith(p.Target[1..]) : domain.ToString().Equals(p.Target));
var aclsThatApply = aclsThatApplyMatch.Union(aclsThatApplyRegex);
if (IPAddress.TryParse(srcAddress, out var srcIp) && aclsThatApply.Any(acl => acl.Networks.Any(p => p.Subnet.Equals(srcIp))))
return AuthOk(user);
return aclsThatApply.Any(acl => acl.Groups.Any(aclGroup => user.Groups.Any(userGroup => userGroup == aclGroup)) || acl.Users.Any(u => u == user))
? AuthOk(user)
: Unauthorized();
}
private IActionResult AuthOk(User user) {
Response.Headers.Add("Remote-User", user.Username);
Response.Headers.Add("Remote-Groups", user.Groups.Select(p => p.Name).Aggregate((s, s1) => $"{s},{s1}"));
Response.Headers.Add("Remote-Name", user.Username);
Response.Headers.Add("Remote-Email", user.Email);
return Ok();
}
}

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
Be Gay, Do Crimes License
Copyright (c) 2023 Laura Hausmann
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:
Be Gay
Do Crimes
The above copyright notice and this permission notice 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.

View file

@ -0,0 +1,32 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations {
[DbContext(typeof(DatabaseContext))]
[Migration("20230407214043_3DA5B7D1-3C1E-496D-9A32-07EAFD2C09D7")]
partial class _3DA5B7D13C1E496D9A3207EAFD2C09D7 {
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) {
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b => {
b.Property<string>("Key").HasColumnType("TEXT").HasColumnName("Key");
b.Property<string>("Value").IsRequired().HasColumnType("TEXT").HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations {
/// <inheritdoc />
public partial class _3DA5B7D13C1E496D9A3207EAFD2C09D7 : Migration {
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) {
migrationBuilder.CreateTable(name: "Config",
columns: table => new {
Key = table.Column<string>(type: "TEXT", nullable: false), Value = table.Column<string>(type: "TEXT", nullable: false)
}, constraints: table => { table.PrimaryKey("PK_Config", x => x.Key); });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder) {
migrationBuilder.DropTable(name: "Config");
}
}
}

View file

@ -0,0 +1,61 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230528173603_90386D40-0C64-4543-99A3-401D20F01FA8")]
partial class _90386D400C64454399A3401D20F01FA8
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _90386D400C64454399A3401D20F01FA8 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Username = table.Column<string>(type: "TEXT", nullable: false),
Password = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View file

@ -0,0 +1,110 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230528183503_D99E1078-A3A4-43E9-BA5A-5E0B00723B68")]
partial class D99E1078A3A443E9BA5A5E0B00723B68
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,113 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class D99E1078A3A443E9BA5A5E0B00723B68 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Username",
table: "Users",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AlterColumn<string>(
name: "Password",
table: "Users",
type: "TEXT",
nullable: true,
oldClrType: typeof(string),
oldType: "TEXT");
migrationBuilder.AddColumn<string>(
name: "Reference",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "Groups",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Groups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "GroupUser",
columns: table => new
{
GroupsId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GroupUser", x => new { x.GroupsId, x.UserId });
table.ForeignKey(
name: "FK_GroupUser_Groups_GroupsId",
column: x => x.GroupsId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_GroupUser_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_GroupUser_UserId",
table: "GroupUser",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GroupUser");
migrationBuilder.DropTable(
name: "Groups");
migrationBuilder.DropColumn(
name: "Reference",
table: "Users");
migrationBuilder.AlterColumn<string>(
name: "Username",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Password",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
}
}
}

View file

@ -0,0 +1,114 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230528212912_B1C2E345-D205-4C17-BC56-1E500FC875B3")]
partial class B1C2E345D2054C17BC561E500FC875B3
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class B1C2E345D2054C17BC561E500FC875B3 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Email",
table: "Users",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Email",
table: "Users");
}
}
}

View file

@ -0,0 +1,120 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230529020954_02A5FFA6-D336-4CB6-B7DE-3BACD30101F0")]
partial class _02A5FFA6D3364CB6B7DE3BACD30101F0
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _02A5FFA6D3364CB6B7DE3BACD30101F0 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Iteration",
table: "Users",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Iteration",
table: "Users");
}
}
}

View file

@ -0,0 +1,255 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601004614_32374F74-E406-4989-A326-8973BEBA3593")]
partial class _32374F74E4064989A3268973BEBA3593
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("TargetRegex")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,148 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _32374F74E4064989A3268973BEBA3593 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ACLs",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Target = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ACLs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Networks",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Subnet = table.Column<string>(type: "TEXT", nullable: false),
Subnet1 = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Networks", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ACLGroup",
columns: table => new
{
ACLId = table.Column<int>(type: "INTEGER", nullable: false),
GroupsId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ACLGroup", x => new { x.ACLId, x.GroupsId });
table.ForeignKey(
name: "FK_ACLGroup_ACLs_ACLId",
column: x => x.ACLId,
principalTable: "ACLs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ACLGroup_Groups_GroupsId",
column: x => x.GroupsId,
principalTable: "Groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ACLUser",
columns: table => new
{
ACLId = table.Column<int>(type: "INTEGER", nullable: false),
UsersId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ACLUser", x => new { x.ACLId, x.UsersId });
table.ForeignKey(
name: "FK_ACLUser_ACLs_ACLId",
column: x => x.ACLId,
principalTable: "ACLs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ACLUser_Users_UsersId",
column: x => x.UsersId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ACLNetwork",
columns: table => new
{
ACLId = table.Column<int>(type: "INTEGER", nullable: false),
NetworksId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ACLNetwork", x => new { x.ACLId, x.NetworksId });
table.ForeignKey(
name: "FK_ACLNetwork_ACLs_ACLId",
column: x => x.ACLId,
principalTable: "ACLs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ACLNetwork_Networks_NetworksId",
column: x => x.NetworksId,
principalTable: "Networks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ACLGroup_GroupsId",
table: "ACLGroup",
column: "GroupsId");
migrationBuilder.CreateIndex(
name: "IX_ACLNetwork_NetworksId",
table: "ACLNetwork",
column: "NetworksId");
migrationBuilder.CreateIndex(
name: "IX_ACLUser_UsersId",
table: "ACLUser",
column: "UsersId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ACLGroup");
migrationBuilder.DropTable(
name: "ACLNetwork");
migrationBuilder.DropTable(
name: "ACLUser");
migrationBuilder.DropTable(
name: "Networks");
migrationBuilder.DropTable(
name: "ACLs");
}
}
}

View file

@ -0,0 +1,261 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601011328_1820F0CE-92F5-47FC-BE0A-DA986052B473")]
partial class _1820F0CE92F547FCBE0ADA986052B473
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _1820F0CE92F547FCBE0ADA986052B473 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "TargetIsRegex",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TargetIsRegex",
table: "ACLs");
}
}
}

View file

@ -0,0 +1,267 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601012412_41A84365-F764-4EE5-9FA5-B219DE00478B")]
partial class _41A84365F7644EE59FA5B219DE00478B
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true)
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _41A84365F7644EE59FA5B219DE00478B : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Enabled",
table: "ACLs");
}
}
}

View file

@ -0,0 +1,267 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601012857_CECB26F6-BA85-4EF7-AE9D-B937E1C08562")]
partial class CECB26F6BA854EF7AE9DB937E1C08562
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true)
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class CECB26F6BA854EF7AE9DB937E1C08562 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View file

@ -0,0 +1,268 @@
// <auto-generated />
using System;
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601012928_AA44F804-8BF6-4DD5-8CAA-270ADB8CE533")]
partial class AA44F8048BF64DD58CAA270ADB8CE533
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool?>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true)
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class AA44F8048BF64DD58CAA270ADB8CE533 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: true,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldDefaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldNullable: true,
oldDefaultValue: true);
}
}
}

View file

@ -0,0 +1,265 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601024525_08CE75A7-A8A8-4B34-9DC4-DFA5960EBB1E")]
partial class _08CE75A7A8A84B349DC4DFA5960EBB1E
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _08CE75A7A8A84B349DC4DFA5960EBB1E : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: false,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldNullable: true,
oldDefaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: true,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER");
}
}
}

View file

@ -0,0 +1,265 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601024629_108887D2-8B53-462E-870D-2175E0BC031C")]
partial class _108887D28B53462E870D2175E0BC031C
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _108887D28B53462E870D2175E0BC031C : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View file

@ -0,0 +1,267 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601024718_85193883-1F4F-42DD-AAE7-4FC4C6C701C0")]
partial class _851938831F4F42DDAAE74FC4C6C701C0
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true)
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class _851938831F4F42DDAAE74FC4C6C701C0 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldDefaultValue: true);
}
}
}

View file

@ -0,0 +1,265 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230601024742_CF94C7ED-B8BA-4672-A13B-3333C9774810")]
partial class CF94C7EDB8BA4672A13B3333C9774810
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Authinator.Migrations
{
/// <inheritdoc />
public partial class CF94C7EDB8BA4672A13B3333C9774810 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldDefaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<bool>(
name: "Enabled",
table: "ACLs",
type: "INTEGER",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER");
}
}
}

View file

@ -0,0 +1,262 @@
// <auto-generated />
using Authinator.Backend.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Authinator.Migrations
{
[DbContext(typeof(DatabaseContext))]
partial class DatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.4");
modelBuilder.Entity("ACLGroup", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "GroupsId");
b.HasIndex("GroupsId");
b.ToTable("ACLGroup");
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("NetworksId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "NetworksId");
b.HasIndex("NetworksId");
b.ToTable("ACLNetwork");
});
modelBuilder.Entity("ACLUser", b =>
{
b.Property<int>("ACLId")
.HasColumnType("INTEGER");
b.Property<int>("UsersId")
.HasColumnType("INTEGER");
b.HasKey("ACLId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("ACLUser");
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.ACL", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Enabled")
.HasColumnType("INTEGER")
.HasColumnName("Enabled");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.Property<string>("Target")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Target");
b.Property<bool>("TargetIsRegex")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false)
.HasColumnName("TargetIsRegex");
b.HasKey("Id");
b.ToTable("ACLs", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Config", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT")
.HasColumnName("Key");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("Key");
b.ToTable("Config", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Group", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Name");
b.HasKey("Id");
b.ToTable("Groups", (string)null);
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.Network", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Subnet");
b.Property<string>("Subnet")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Networks", null, t =>
{
t.Property("Subnet")
.HasColumnName("Subnet1");
});
});
modelBuilder.Entity("Authinator.Backend.Database.Tables.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("Email");
b.Property<int>("Iteration")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0)
.HasColumnName("Iteration");
b.Property<string>("Password")
.HasColumnType("TEXT")
.HasColumnName("Password");
b.Property<string>("Reference")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Reference");
b.Property<string>("Username")
.HasColumnType("TEXT")
.HasColumnName("Username");
b.HasKey("Id");
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GroupUser", b =>
{
b.Property<int>("GroupsId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("GroupsId", "UserId");
b.HasIndex("UserId");
b.ToTable("GroupUser");
});
modelBuilder.Entity("ACLGroup", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLNetwork", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.Network", null)
.WithMany()
.HasForeignKey("NetworksId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ACLUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.ACL", null)
.WithMany()
.HasForeignKey("ACLId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GroupUser", b =>
{
b.HasOne("Authinator.Backend.Database.Tables.Group", null)
.WithMany()
.HasForeignKey("GroupsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Authinator.Backend.Database.Tables.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

179
Pages/Admin.cshtml Normal file
View file

@ -0,0 +1,179 @@
@page
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@using Microsoft.EntityFrameworkCore
@model AdminModel
@{
ViewData["Title"] = "Admin";
var db = new DatabaseContext();
async void Td(string? value, string placeholder = "unset") {
if (string.IsNullOrWhiteSpace(value)) {
<td>
<i>@placeholder</i>
</td>
}
else {
<td>@value</td>
}
}
}
<h1 class="display-6">Users</h1>
<table class="table table-striped table-hover table-bordered align-middle">
<thead>
<th>ID</th>
<th>Reference</th>
<th>Username</th>
<th>Email</th>
<th>Groups</th>
<th>Actions</th>
</thead>
<tbody>
@foreach (var user in db.Users.Include(p => p.Groups)) {
<tr>
<td>@user.Id</td>
@{
Td(user.Reference);
Td(user.Username);
Td(user.Email);
Td(user.Groups.Select(p => p.Name).DefaultIfEmpty().Aggregate((s, s1) => $"{s}, {s1}"), "none");
}
<td>
<div class="dropdown">
@if (user.IsSignupComplete) {
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
}
else {
<button class="btn btn-sm btn-outline-success dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
}
<ul class="dropdown-menu">
@if (!user.IsSignupComplete) {
<li>
<button class="dropdown-item" onclick="copyToClipboard(location.protocol + '//' + location.host + '/FinishRegistration/' + '@HttpUtility.UrlEncode(user.GetSignupToken())')"><i class="bi bi-check2-circle"></i>&nbsp; Copy signup link</button>
</li>
<li>
<a href="/Admin/User/@user.Id/Edit" class="dropdown-item"><i class="bi bi-pencil-fill"></i>&nbsp; Edit</a>
</li>
<li>
<button class="dropdown-item text-danger"><i class="bi bi-trash-fill"></i>&nbsp; Delete</button>
</li>
}
else {
<li>
<button class="dropdown-item" onclick="copyToClipboard(location.protocol + '//' + location.host + '/User/@user.Id/Reset/' + '@HttpUtility.UrlEncode(user.GetPwResetToken())')"><i class="bi bi-person-fill-lock"></i>&nbsp; Reset password</button>
</li>
<li>
<a href="/Admin/User/@user.Id/Edit" class="dropdown-item"><i class="bi bi-pencil-fill"></i>&nbsp; Edit</a>
</li>
<li>
<button class="dropdown-item text-warning"><i class="bi bi-exclamation-triangle-fill"></i>&nbsp; Disable</button>
</li>
<li>
<button class="dropdown-item text-danger"><i class="bi bi-trash-fill"></i>&nbsp; Delete</button>
</li>
}
</ul>
</div>
</td>
</tr>
}
</tbody>
</table>
<b>Add user</b><br/>
<form method="POST">
<input type="text" name="reference" placeholder="Reference" required/>
<button type="submit" name="action" value="add_user">Submit</button>
</form>
<br/>
<br/>
<h1 class="display-6">Groups</h1>
<table class="table table-striped table-hover table-bordered align-middle">
<thead>
<th>ID</th>
<th>Name</th>
<th>Actions</th>
</thead>
<tbody>
@foreach (var group in db.Groups) {
<tr>
<td>@group.Id</td>
<td>@group.Name</td>
<td>
<div class="dropdown">
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li>
<a href="/Admin/Group/@group.Id/Edit" class="dropdown-item"><i class="bi bi-pencil-fill"></i>&nbsp; Edit</a>
</li>
<li>
<button class="dropdown-item text-danger"><i class="bi bi-trash-fill"></i>&nbsp; Delete</button>
</li>
</ul>
</div>
</td>
</tr>
}
</tbody>
</table>
<b>Add group</b><br/>
<form method="POST">
<input type="text" name="name" placeholder="Group name" required/>
<button type="submit" name="action" value="add_group">Submit</button>
</form>
<br/>
<br/>
<h1 class="display-6">ACLs</h1>
<table class="table table-striped table-hover table-bordered align-middle">
<thead>
<th>ID</th>
<th>Name</th>
<th>Target</th>
<th>Actions</th>
</thead>
<tbody>
@foreach (var acl in db.ACLs) {
<tr>
<td>@acl.Id</td>
<td>@acl.Name</td>
<td><span class="px-2 py-1 rounded bg-body-tertiary font-monospace">@acl.Target</span></td>
<td>
<div class="dropdown">
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li>
<a href="/Admin/ACL/@acl.Id/Edit" class="dropdown-item"><i class="bi bi-pencil-fill"></i>&nbsp; Edit</a>
</li>
<li>
<button class="dropdown-item text-danger"><i class="bi bi-trash-fill"></i>&nbsp; Delete</button>
</li>
</ul>
</div>
</td>
</tr>
}
</tbody>
</table>
<b>Add ACL</b><br/>
<form method="POST">
<input type="text" name="name" placeholder="ACL name" required/>
<input type="text" name="target" placeholder="Target" required/>
<button type="submit" name="action" value="add_acl">Submit</button>
</form>

40
Pages/Admin.cshtml.cs Normal file
View file

@ -0,0 +1,40 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class AdminModel : PageModel {
public IActionResult OnGet() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
return Page();
}
public async Task<IActionResult> OnPost() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
if (Request.Form["action"] == "add_user") {
await db.Users.AddAsync(new User { Reference = Request.Form["reference"]! });
await db.SaveChangesAsync();
}
if (Request.Form["action"] == "add_group") {
await db.Groups.AddAsync(new Group { Name = Request.Form["name"]! });
await db.SaveChangesAsync();
}
if (Request.Form["action"] == "add_acl") {
await db.ACLs.AddAsync(new ACL { Name = Request.Form["name"]!, Target = Request.Form["target"]! });
await db.SaveChangesAsync();
}
return Redirect("/Admin");
}
}

110
Pages/AdminEditACL.cshtml Normal file
View file

@ -0,0 +1,110 @@
@page "/Admin/ACL/{id:int}/Edit"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@using Microsoft.EntityFrameworkCore
@model AdminEditACLModel
@{
ViewData["Title"] = "Edit ACL - Admin";
var acl = Model.ModelACL;
var db = new DatabaseContext();
var statusIcon = acl.Enabled ? "bi-check-circle-fill" : "bi-dash-circle-dotted";
var statusText = acl.Enabled ? "ACL is currently enabled" : "ACL is currently disabled";
}
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3 fs-4">
Editing ACL: <span class="bg-body-secondary rounded ms-1 px-2 py-1 fw-light">@acl.Name</span>
<span class="px-2 py-1 rounded fs-4 bg-body-tertiary">
<span class="fs-6 align-middle">
<i class="bi @statusIcon"></i> @statusText
</span>
</span>
</div>
<div class="mb-3">
<label for="name" class="form-label">ACL name</label>
<input class="form-control" type="text" id="name" name="name" value="@acl.Name" required/>
</div>
<div class="mb-2">
<div class="form-check">
@if (acl.Enabled) {
<input class="form-check-input" type="checkbox" id="enabled" name="enabled" checked>
}
else {
<input class="form-check-input" type="checkbox" id="enabled" name="enabled">
}
<label for="enabled" class="form-label">Enable ACL</label>
</div>
</div>
<div class="mb-3">
<label for="target" class="form-label">Target</label>
<input class="form-control" type="text" id="target" name="target" placeholder="*.domain.tld" value="@acl.Target" required/>
</div>
<div class="mb-2">
<div class="form-check">
@if (acl.TargetIsRegex) {
<input class="form-check-input" type="checkbox" id="target-is-regex" name="target-is-regex" checked>
}
else {
<input class="form-check-input" type="checkbox" id="target-is-regex" name="target-is-regex">
}
<label for="target-is-regex" class="form-label">Regex matching</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Allow groups?</label>
@foreach (var group in db.Groups) {
<div class="form-check">
@if (acl.Groups.Any(p => p == group)) {
<input class="form-check-input" type="checkbox" id="check-group-@group.Id" name="group" value="@group.Id" checked>
}
else {
<input class="form-check-input" type="checkbox" id="check-group-@group.Id" name="group" value="@group.Id">
}
<label class="form-check-label" for="check-group-@group.Id">
@group.Name
</label>
</div>
}
</div>
<div class="mb-3">
<label class="form-label">Allow users?</label>
@foreach (var user in db.Users) {
<div class="form-check">
@if (acl.Users.Any(p => p == user)) {
<input class="form-check-input" type="checkbox" id="check-user-@user.Id" name="user" value="@user.Id" checked>
}
else {
<input class="form-check-input" type="checkbox" id="check-user-@user.Id" name="user" value="@user.Id">
}
<label class="form-check-label" for="check-user-@user.Id">
@user.Username <span class="px-1 bg-body-secondary">@user.Reference</span>
</label>
</div>
}
</div>
<div class="mb-3">
<label class="form-label">Allow networks?</label>
@foreach (var network in db.Networks) {
<div class="form-check">
@if (acl.Networks.Any(p => p == network)) {
<input class="form-check-input" type="checkbox" id="check-network-@network.Id" name="user" value="@network.Id" checked>
}
else {
<input class="form-check-input" type="checkbox" id="check-network-@network.Id" name="user" value="@network.Id">
}
<label class="form-check-label" for="check-network-@network.Id">
@network.Name <span class="px-1 bg-body-secondary">@network.Subnet.ToString()</span>
</label>
</div>
}
</div>
<button class="btn btn-primary" type="submit" name="action" value="save">Save changes</button>
</form>

View file

@ -0,0 +1,76 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace Authinator.Pages;
public class AdminEditACLModel : PageModel {
public ACL ModelACL = null!;
public IActionResult OnGet() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
if (!db.ACLs.Any(p => p.Id == id)) {
return BadRequest();
}
ModelACL = db.ACLs.Include(p => p.Groups).First(p => p.Id == id);
return Page();
}
public async Task<IActionResult> OnPost() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
if (Request.Form["action"] == "save") {
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
if (!db.Users.Any(p => p.Id == id)) {
return BadRequest();
}
var user = db.Users.Include(p => p.Groups).First(p => p.Id == id);
var newReference = Request.Form["reference"].ToString();
var newUsername = Request.Form["username"].ToString();
var newEmail = Request.Form["email"].ToString();
var newGroups = Request.Form["group"].Select(int.Parse!).Select(p => db.Groups.First(q => q.Id == p)).ToList();
if (!string.IsNullOrWhiteSpace(newReference) && newReference != user.Reference) {
if (db.Users.Any(p => p.Reference == newReference)) {
return BadRequest();
}
user.Reference = newReference;
}
if (!string.IsNullOrWhiteSpace(newUsername) && newUsername != user.Username) {
if (db.Users.Any(p => p.Username == newUsername)) {
return BadRequest();
}
user.Username = newUsername;
}
if (!string.IsNullOrWhiteSpace(newEmail) && newEmail != user.Email) {
user.Email = newEmail;
}
user.Groups = newGroups;
await db.SaveChangesAsync();
}
return Redirect("/Admin");
}
}

View file

@ -0,0 +1,65 @@
@page "/Admin/User/{id:int}/Edit"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@using Microsoft.EntityFrameworkCore
@model AdminEditUserModel
@{
ViewData["Title"] = "Edit User - Admin";
var user = Model.ModelUser;
var db = new DatabaseContext();
var statusIcon = user.IsSignupComplete ? "bi-check-circle-fill" : "bi-clock-fill";
var statusText = user.IsSignupComplete ? "User is active" : "Awaiting activation";
}
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3 fs-4">
Editing User: <span class="bg-body-secondary rounded ms-1 px-2 py-1 fw-light">@user.Reference</span>
<span class="px-2 py-1 rounded fs-4 bg-body-tertiary">
<span class="fs-6 align-middle">
<i class="bi @statusIcon"></i> @statusText
</span>
</span>
</div>
<div class="mb-3">
<label for="reference" class="form-label">Admin reference</label>
<input class="form-control" type="text" id="reference" name="reference" value="@user.Reference" required/>
</div>
@if (user.IsSignupComplete) {
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input class="form-control" type="text" id="username" name="username" placeholder="theirusername" value="@user.Username" required/>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-Mail</label>
<input class="form-control" type="email" id="email" name="email" placeholder="their@email.tld" value="@user.Email" required/>
</div>
}
<div class="mb-3">
<label class="form-label">Groups</label>
@foreach (var group in db.Groups) {
<div class="form-check">
@if (user.Groups.Any(p => p == group)) {
if (group.Name == ConfigCache.AdminGroup && user == HttpContext.GetRemoteUser(db)) {
<input class="form-check-input" type="checkbox" id="check-group-@group.Id" name="group" value="@group.Id" checked disabled>
}
else {
<input class="form-check-input" type="checkbox" id="check-group-@group.Id" name="group" value="@group.Id" checked>
}
}
else {
<input class="form-check-input" type="checkbox" id="check-group-@group.Id" name="group" value="@group.Id">
}
<label class="form-check-label" for="check-group-@group.Id">
@group.Name
</label>
</div>
}
</div>
<button class="btn btn-primary" type="submit" name="action" value="save">Save changes</button>
</form>

View file

@ -0,0 +1,76 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace Authinator.Pages;
public class AdminEditUserModel : PageModel {
public User ModelUser = null!;
public IActionResult OnGet() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
if (!db.Users.Any(p => p.Id == id)) {
return BadRequest();
}
ModelUser = db.Users.Include(p => p.Groups).First(p => p.Id == id);
return Page();
}
public async Task<IActionResult> OnPost() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAdmin(db))
return StatusCode(StatusCodes.Status403Forbidden);
if (Request.Form["action"] == "save") {
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
if (!db.Users.Any(p => p.Id == id)) {
return BadRequest();
}
var user = db.Users.Include(p => p.Groups).First(p => p.Id == id);
var newReference = Request.Form["reference"].ToString();
var newUsername = Request.Form["username"].ToString();
var newEmail = Request.Form["email"].ToString();
var newGroups = Request.Form["group"].Select(int.Parse!).Select(p => db.Groups.First(q => q.Id == p)).ToList();
if (!string.IsNullOrWhiteSpace(newReference) && newReference != user.Reference) {
if (db.Users.Any(p => p.Reference == newReference)) {
return BadRequest();
}
user.Reference = newReference;
}
if (!string.IsNullOrWhiteSpace(newUsername) && newUsername != user.Username) {
if (db.Users.Any(p => p.Username == newUsername)) {
return BadRequest();
}
user.Username = newUsername;
}
if (!string.IsNullOrWhiteSpace(newEmail) && newEmail != user.Email) {
user.Email = newEmail;
}
user.Groups = newGroups;
await db.SaveChangesAsync();
}
return Redirect("/Admin");
}
}

28
Pages/Error.cshtml Normal file
View file

@ -0,0 +1,28 @@
@page
@using Microsoft.AspNetCore.WebUtilities
@model ErrorModel
@{
ViewData["Title"] = $"Error {Response.StatusCode}";
}
@if (Response.StatusCode == 200) {
<h1 class="text-success">No error occured.</h1>
<p>Why are you here?</p>
return;
}
<h1 class="text-danger">Error @Response.StatusCode: @ReasonPhrases.GetReasonPhrase(Response.StatusCode)</h1>
@if (Response.StatusCode == 404) {
<p>The page you requested does not exist on this server.</p>
}
else {
<p>An error occurred while processing your request.</p>
}
@if (Model.ShowRequestId) {
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

16
Pages/Error.cshtml.cs Normal file
View file

@ -0,0 +1,16 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true), IgnoreAntiforgeryToken]
public class ErrorModel : PageModel {
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet() {
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

17
Pages/Index.cshtml Normal file
View file

@ -0,0 +1,17 @@
@page
@using Authinator.Backend.Utils
@model IndexModel
@{
ViewData["Title"] = "Home";
}
<div class="text-center">
<h1 class="display-4">
Welcome
</h1>
<p>
This is the default landing page of <a href="@ApplicationInformation.Repository">@ApplicationInformation.Project</a>, a work in progress ASP.NET Core project by <a href="https://zotan.pw">~zotan</a>.
<br/>
Expect to see something more useful here once project development continues.
</p>
</div>

14
Pages/Index.cshtml.cs Normal file
View file

@ -0,0 +1,14 @@
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class IndexModel : PageModel {
public IActionResult OnGet() {
if (Globals.NoActiveAdminUser)
return Redirect("/FinishRegistration");
return Page();
}
}

22
Pages/Login.cshtml Normal file
View file

@ -0,0 +1,22 @@
@page "/Login"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@model LoginModel
@{
ViewData["Title"] = "Log in";
}
<h1 class="display-6">Log in</h1>
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input class="form-control" type="text" id="username" name="username" placeholder="" required/>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input class="form-control" type="password" id="password" name="password" placeholder="" required/>
</div>
<button class="btn btn-primary" type="submit" name="action" value="login">Submit</button>
</form>

41
Pages/Login.cshtml.cs Normal file
View file

@ -0,0 +1,41 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class LoginModel : PageModel {
public IActionResult OnGet() {
var db = new DatabaseContext();
if (HttpContext.IsAuthenticated(db))
return Redirect("/User");
return Page();
}
public IActionResult OnPost() {
var db = new DatabaseContext();
if (HttpContext.IsAuthenticated(db))
return Redirect("/User");
if (Request.Form["action"] == "login") {
var user = db.Users.FirstOrDefault(p => p.Username == Request.Form["username"].ToString());
if (user == null) {
return Redirect("/Login");
}
if (!user.ValidatePassword(Request.Form["password"].ToString()))
return Redirect("/Login");
Response.AppendUnencodedCookie(ConfigCache.CookieName, user.GetAuthToken());
return Redirect("/User");
}
return Redirect("/Login");
}
}

103
Pages/Shared/_Layout.cshtml Normal file
View file

@ -0,0 +1,103 @@
@using Authinator.Backend.Utils
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using System.Web
@using Authinator.Backend.Database
@{
var db = new DatabaseContext();
}
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
@await RenderSectionAsync("Meta", false)
<title>@ViewData["Title"] - @ApplicationInformation.Project</title>
<link rel="stylesheet" href="~/lib/bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="~/lib/bootstrap/bootstrap-icons.min.css">
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
@await RenderSectionAsync("Stylesheets", false)
</head>
<body class="d-flex flex-column min-vh-100">
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3">
@* ReSharper disable once HeuristicUnreachableCode *@
<div class="@(ApplicationInformation.WideContainers ? "container-fluid" : "container")">
<a class="navbar-brand align-middle" asp-area="" asp-page="/Index">
<img src="/favicon.svg" alt="Logo">
</a>
<a class="navbar-brand align-middle" asp-area="" asp-page="/Index">@ApplicationInformation.Project</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link" asp-area="" asp-page="/Index">Home</a>
</li>
@if (Context.IsAuthenticated(db) && Context.GetRemoteUser(db) != null) {
<li class="nav-item">
<a class="nav-link" asp-area="" asp-page="/User">User</a>
</li>
}
@if (Context.IsAdmin(db)) {
<li class="nav-item">
<a class="nav-link" asp-area="" asp-page="/Admin">Admin</a>
</li>
}
</ul>
<ul class="navbar-nav">
<li class="nav-item">
@if (!Context.IsAuthenticated(db)) {
<a class="nav-link" href="/Login?rd=@HttpUtility.UrlEncode(Context.Request.Path.ToString())">
<span class="fw-semibold">Log in</span>
</a>
}
else if (AuthUtils.OverrideAuth) {
<span>
<b>&#x26A0; Auth system overriden &#x26A0;</b>
</span>
}
else {
<span>Authenticated as <b>@Context.GetRemoteUsername(db)</b></span>
}
</li>
</ul>
</div>
</div>
</nav>
</header>
@* ReSharper disable once HeuristicUnreachableCode *@
<div class="@(ApplicationInformation.WideContainers ? "container-custom" : "container") ">
<main role="main" class="pb-3">
@if (AuthUtils.OverrideAuth) {
<div class="alert alert-warning" role="alert">
You are connected as the fallback user '@Context.GetRemoteUsername(db)' because this program was started with the '@AuthUtils.OverrideAuthCli' command line argument. If this is a production deployment, please make sure your configuration is correct.
</div>
}
@RenderBody()
</main>
</div>
<footer class="footer bg-body-tertiary mt-auto py-3">
<div class="container text-muted text-center">
@if (ApplicationInformation.HasVersion) {
<div>
Served by @Environment.MachineName running <a class="footerlink" href="@ApplicationInformation.Repository/commit/@ApplicationInformation.LinkVersion" target="_blank">@ApplicationInformation.Project @ApplicationInformation.Version</a> on .NET @Environment.Version
</div>
}
else {
<div>
Served by @Environment.MachineName running <a href="@ApplicationInformation.Repository">@ApplicationInformation.Project</a> (git revision unknown) on .NET @Environment.Version
</div>
}
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", false)
</body>
</html>

View file

@ -0,0 +1,49 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View file

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

49
Pages/User.cshtml Normal file
View file

@ -0,0 +1,49 @@
@page
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@using Microsoft.EntityFrameworkCore
@model UserModel
@{
ViewData["Title"] = "Edit User";
var user = Model.ModelUser;
}
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3 fs-4">
Editing User: <span class="bg-body-secondary rounded ms-1 px-2 py-1 fw-light">@user.Username</span>
<span class="px-2 py-1 rounded fs-4 bg-body-tertiary">
<span class="fs-6 align-middle">
<i class="bi bi-check-circle-fill"></i> User is active
</span>
</span>
</div>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input class="form-control" type="text" id="username" name="username" placeholder="theirusername" value="@user.Username" required/>
<div class="form-text"><i class="bi bi-exclamation-triangle"></i> Changing your username will break SSO applications, use with caution!</div>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-Mail</label>
<input class="form-control" type="email" id="email" name="email" placeholder="their@email.tld" value="@user.Email" required/>
<div class="form-text"><i class="bi bi-envelope-check-fill"></i> Please use a valid email address.</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input class="form-control" type="password" id="password" name="password" placeholder="leave blank to keep unchanged" minlength="12"/>
<div class="form-text"><i class="bi bi-info-circle"></i> Changing your password will invalidate all sessions, including this one.</div>
</div>
<div class="mb-3">
<label class="form-label">Groups</label>
<ul>
@foreach (var group in user.Groups) {
<li>@group.Name</li>
}
</ul>
</div>
<button class="btn btn-primary" type="submit" name="action" value="save">Save changes</button>
</form>

62
Pages/User.cshtml.cs Normal file
View file

@ -0,0 +1,62 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class UserModel : PageModel {
public User ModelUser = null!;
public IActionResult OnGet() {
var db = new DatabaseContext();
if (!HttpContext.IsAuthenticated(db))
return Redirect("/Login");
#pragma warning disable CS8601
ModelUser = HttpContext.GetRemoteUser(db);
#pragma warning restore CS8601
if (ModelUser == null)
return Redirect("/");
return Page();
}
public async Task<IActionResult> OnPost() {
var db = new DatabaseContext();
if (!Request.HttpContext.IsAuthenticated(db))
return Redirect("/Login");
if (Request.Form["action"] == "save") {
var user = HttpContext.GetRemoteUser(db)!;
var newUsername = Request.Form["username"].ToString();
var newPassword = Request.Form["password"].ToString();
var newEmail = Request.Form["email"].ToString();
if (!string.IsNullOrWhiteSpace(newUsername) && newUsername != user.Username) {
if (db.Users.Any(p => p.Username == newUsername)) {
return BadRequest();
}
user.Username = newUsername;
}
if (!string.IsNullOrWhiteSpace(newEmail) && newEmail != user.Email) {
user.Email = newEmail;
}
if (!string.IsNullOrWhiteSpace(newPassword) && newPassword.Length >= 8) {
user.SetPassword(newPassword);
user.Iteration++;
}
await db.SaveChangesAsync();
}
return Redirect("/");
}
}

32
Pages/UserRegister.cshtml Normal file
View file

@ -0,0 +1,32 @@
@page "/FinishRegistration/{token}"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@model UserRegisterModel
@{
ViewData["Title"] = "Finish registration";
}
<h1 class="display-6">Finish registration</h1>
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3">
<label for="regtoken" class="form-label">Registration token</label>
<input class="form-control" type="text" id="regtoken" name="regtoken" value="@RouteData.Values["token"]?.ToString()!.FixUrlEncodedBase64()" required disabled/>
<div class="form-text"><i class="bi bi-check-circle-fill"></i> Token valid.</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input class="form-control" type="text" id="username" name="username" placeholder="myusername" required/>
</div>
<div class="mb-3">
<label for="email" class="form-label">E-Mail</label>
<input class="form-control" type="email" id="email" name="email" placeholder="my@email.tld" required/>
<div class="form-text"><i class="bi bi-envelope-check-fill"></i> Please use a valid email address.</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input class="form-control" type="password" id="password" name="password" placeholder="supersecurepassword123" minlength="12" required/>
</div>
<button class="btn btn-primary" type="submit" name="action" value="finish_registration">Submit</button>
</form>

View file

@ -0,0 +1,81 @@
using System.Web;
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
namespace Authinator.Pages;
public class UserRegisterModel : PageModel {
public IActionResult OnGet() {
var db = new DatabaseContext();
var token = (string)(RouteData.Values["token"] ?? string.Empty);
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("register:") || token.Split(":").Length != 3)
return StatusCode(StatusCodes.Status403Forbidden);
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return StatusCode(StatusCodes.Status403Forbidden);
var id = int.Parse(message.Split(":")[1]);
var user = db.Users.FirstOrDefault(p => p.Id == id);
if (user == null || user.IsSignupComplete) {
return BadRequest();
}
return Page();
}
public async Task<IActionResult> OnPost() {
if (Request.Form["action"] == "finish_registration") {
var db = new DatabaseContext();
var token = (string)(RouteData.Values["token"] ?? string.Empty);
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("register:") || token.Split(":").Length != 3)
return StatusCode(StatusCodes.Status403Forbidden);
var index = token.LastIndexOf(":", StringComparison.Ordinal) + 1;
var hmac = token[index..].FixUrlEncodedBase64();
var message = token[..index];
if (message.Hmac(ConfigCache.HmacSecret) != hmac)
return StatusCode(StatusCodes.Status403Forbidden);
var id = int.Parse(message.Split(":")[1]);
var user = db.Users.Include(p => p.Groups).FirstOrDefault(p => p.Id == id);
if (user == null || user.IsSignupComplete) {
return BadRequest();
}
if (db.Users.Any(p => p.Username == Request.Form["username"].ToString())) {
return BadRequest(); //TODO "user already exists" error message
}
if (string.IsNullOrWhiteSpace(Request.Form["password"]) || ((string)Request.Form["password"])!.Length < 8) {
return BadRequest(); //TODO "password too short" error message
}
user.Email = Request.Form["email"];
user.Username = Request.Form["username"];
user.SetPassword(Request.Form["password"]!);
await db.SaveChangesAsync();
if (Globals.NoActiveAdminUser && user.Groups.Any(p => p.Name == ConfigCache.AdminGroup))
Globals.NoActiveAdminUser = false;
return Redirect("/User");
}
return Redirect("/");
}
}

View file

@ -0,0 +1,26 @@
@page "/FinishRegistration"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@model UserRegisterNoTokenModel
@{
ViewData["Title"] = "Finish registration";
}
@if (Globals.NoActiveAdminUser) {
<div class="alert alert-warning">
No active admin user found, please check the logs for the activation token!
</div>
}
else {
<h1 class="display-6">Finish registration</h1>
}
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3">
<label for="token" class="form-label">Registration token</label>
<input class="form-control" type="text" id="token" name="token" placeholder="register:0:aG1hYw==" required/>
</div>
<button class="btn btn-primary" type="submit" name="action" value="validate_token">Submit</button>
</form>

View file

@ -0,0 +1,23 @@
using System.Web;
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class UserRegisterNoTokenModel : PageModel {
public IActionResult OnGet() {
return Page();
}
public IActionResult OnPost() {
if (Request.Form["action"] == "validate_token") {
var token = Request.Form["token"].ToString();
return Redirect("/FinishRegistration/" + HttpUtility.UrlEncode(token));
}
return Redirect("/FinishRegistration");
}
}

28
Pages/UserReset.cshtml Normal file
View file

@ -0,0 +1,28 @@
@page "/User/{id:int}/Reset/{token}"
@using Authinator.Backend.Utils
@using Authinator.Backend.Database
@using Authinator.Backend.Database.Tables
@using System.Web
@model UserResetModel
@{
ViewData["Title"] = "Reset password";
}
<h1 class="display-6">Reset password</h1>
<form method="POST" class="border rounded px-3 py-3">
<div class="mb-3">
<label for="token" class="form-label">Reset token</label>
<input class="form-control" type="text" id="token" name="resettoken" value="@RouteData.Values["token"]?.ToString()!.FixUrlEncodedBase64()" required disabled/>
<div class="form-text"><i class="bi bi-check-circle-fill"></i> Token valid.</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input class="form-control" type="text" id="username" name="username" value="@Model.ModelUser?.Username" required disabled/>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input class="form-control" type="password" id="password" name="password" placeholder="supersecurepassword123" minlength="12" required/>
<div class="form-text"><i class="bi bi-info-circle"></i> Changing your password will invalidate all sessions.</div>
</div>
<button class="btn btn-primary" type="submit" name="action" value="reset_password">Submit</button>
</form>

60
Pages/UserReset.cshtml.cs Normal file
View file

@ -0,0 +1,60 @@
using Authinator.Backend.Database;
using Authinator.Backend.Database.Tables;
using Authinator.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Authinator.Pages;
public class UserResetModel : PageModel {
public User? ModelUser;
public IActionResult OnGet() {
var db = new DatabaseContext();
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
var token = (string)(RouteData.Values["token"] ?? string.Empty);
var user = db.Users.FirstOrDefault(p => p.Id == id);
if (user is not { IsSignupComplete: true }) {
return BadRequest();
}
if (!user.ValidateResetToken(token)) {
return StatusCode(StatusCodes.Status403Forbidden);
}
ModelUser = user;
return Page();
}
public async Task<IActionResult> OnPost() {
if (Request.Form["action"] == "reset_password") {
var db = new DatabaseContext();
var id = int.Parse((string)(RouteData.Values["id"] ?? string.Empty));
var token = (string)(RouteData.Values["token"] ?? string.Empty);
var user = db.Users.FirstOrDefault(p => p.Id == id);
if (user is not { IsSignupComplete: true }) {
return BadRequest();
}
if (!user.ValidateResetToken(token)) {
return StatusCode(StatusCodes.Status403Forbidden);
}
if (string.IsNullOrWhiteSpace(Request.Form["password"]) || ((string)Request.Form["password"])!.Length < 8) {
return BadRequest(); //TODO "password too short" error message
}
user.SetPassword(Request.Form["password"]!);
user.Iteration++;
await db.SaveChangesAsync();
Response.Cookies.Delete(ConfigCache.CookieName);
return Redirect("/User");
}
return Redirect("/");
}
}

View file

@ -0,0 +1,3 @@
@using Authinator
@namespace Authinator.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

3
Pages/_ViewStart.cshtml Normal file
View file

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View file

@ -0,0 +1,24 @@
{
"profiles": {
"AuthOverride": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:8291",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "--disable-auth --razor-runtime-comp"
},
"Guest": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:9514",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "--razor-runtime-comp"
}
}
}

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Authinator
WIP SSO system.

View file

@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
appsettings.json Normal file
View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

47
wwwroot/css/site.css Normal file
View file

@ -0,0 +1,47 @@
html {
position: relative;
min-height: 100%;
}
.footer {
bottom: 0;
width: 100%;
white-space: normal;
padding: 1em;
}
.form-group {
margin-bottom: 10px;
}
.container-custom, .container-fluid {
margin-left: 1.5%;
margin-right: 1.5%;
}
:root {
--bs-link-hover-color: #b06d09;
--bs-link-hover-color-rgb: 176, 109, 9;
--bs-link-color: #e38d0b;
--bs-link-color-rgb: 227, 141, 11;
--bs-border-color: #343a40;
}
a {
text-decoration-color: rgba(var(--bs-link-color-rgb), 0.3);
text-underline-offset: 2px;
}
nav.navbar {
line-height: 20px;
}
.navbar-brand {
line-height: 20px;
}
.navbar-brand img {
height: 35px;
margin-right: 5px;
}

BIN
wwwroot/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

34
wwwroot/favicon.svg Normal file
View file

@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" height="596pt" version="1.1" viewBox="1 -19 596.717 596" width="596pt">
<g id="surface1">
<path d="M 39.800781 10.316406 L 477.597656 10.316406 C 494.085938 10.316406 507.449219 23.679688 507.449219 40.167969 L 507.449219 438.167969 C 507.449219 454.65625 494.085938 468.019531 477.597656 468.019531 L 39.800781 468.019531 C 23.3125 468.019531 9.949219 454.65625 9.949219 438.167969 L 9.949219 40.167969 C 9.949219 23.679688 23.3125 10.316406 39.800781 10.316406 Z M 39.800781 10.316406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(26.27451%,59.607843%,81.960784%);fill-opacity:1;" />
<path d="M 39.800781 40.167969 L 59.699219 40.167969 L 59.699219 60.066406 L 39.800781 60.066406 Z M 39.800781 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(24.313725%,54.901961%,78.039216%);fill-opacity:1;" />
<path d="M 79.597656 40.167969 L 99.5 40.167969 L 99.5 60.066406 L 79.597656 60.066406 Z M 79.597656 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(24.313725%,54.901961%,78.039216%);fill-opacity:1;" />
<path d="M 119.402344 40.167969 L 139.300781 40.167969 L 139.300781 60.066406 L 119.402344 60.066406 Z M 119.402344 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(24.313725%,54.901961%,78.039216%);fill-opacity:1;" />
<path d="M 119.402344 89.917969 L 557.199219 89.917969 C 573.6875 89.917969 587.050781 103.28125 587.050781 119.765625 L 587.050781 517.765625 C 587.050781 534.253906 573.6875 547.617188 557.199219 547.617188 L 119.402344 547.617188 C 102.914062 547.617188 89.550781 534.253906 89.550781 517.765625 L 89.550781 119.765625 C 89.550781 103.28125 102.914062 89.917969 119.402344 89.917969 Z M 119.402344 89.917969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(52.941176%,80.784314%,85.098039%);fill-opacity:1;" />
<path d="M 557.199219 89.917969 L 537.296875 89.917969 L 93.988281 533.230469 C 99.351562 542.144531 108.992188 547.605469 119.398438 547.617188 L 557.199219 547.617188 C 573.6875 547.617188 587.050781 534.253906 587.050781 517.765625 L 587.050781 119.765625 C 587.050781 103.28125 573.6875 89.917969 557.199219 89.917969 Z M 557.199219 89.917969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(44.313725%,76.862745%,81.960784%);fill-opacity:1;" />
<path d="M 119.402344 119.765625 L 139.300781 119.765625 L 139.300781 139.667969 L 119.402344 139.667969 Z M 119.402344 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 159.199219 119.765625 L 179.101562 119.765625 L 179.101562 139.667969 L 159.199219 139.667969 Z M 159.199219 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 199 119.765625 L 218.902344 119.765625 L 218.902344 139.667969 L 199 139.667969 Z M 199 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 119.402344 159.566406 L 557.199219 159.566406 L 557.199219 179.46875 L 119.402344 179.46875 Z M 119.402344 159.566406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 119.402344 209.316406 L 199 209.316406 L 199 229.21875 L 119.402344 229.21875 Z M 119.402344 209.316406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 119.402344 249.117188 L 179.101562 249.117188 L 179.101562 269.019531 L 119.402344 269.019531 Z M 119.402344 249.117188 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 487.550781 388.417969 L 487.550781 328.71875 L 453.789062 328.71875 C 451.011719 317.929688 446.734375 307.601562 441.074219 298.011719 L 464.953125 274.132812 L 422.734375 231.914062 L 398.855469 255.792969 C 389.265625 250.132812 378.9375 245.855469 368.148438 243.078125 L 368.148438 209.316406 L 308.449219 209.316406 L 308.449219 243.078125 C 297.664062 245.855469 287.335938 250.132812 277.746094 255.792969 L 253.863281 231.914062 L 211.644531 274.132812 L 235.527344 298.011719 C 229.863281 307.59375 225.585938 317.929688 222.808594 328.71875 L 189.050781 328.71875 L 189.050781 388.417969 L 222.808594 388.417969 C 225.585938 399.203125 229.863281 409.53125 235.527344 419.121094 L 211.644531 443.003906 L 253.863281 485.222656 L 277.746094 461.339844 C 287.335938 467.003906 297.664062 471.28125 308.449219 474.058594 L 308.449219 507.816406 L 368.148438 507.816406 L 368.148438 474.058594 C 378.9375 471.28125 389.265625 467.003906 398.855469 461.339844 L 422.734375 485.222656 L 464.953125 443.003906 L 441.074219 419.121094 C 446.734375 409.53125 451.011719 399.203125 453.789062 388.417969 Z M 338.300781 418.265625 C 305.324219 418.265625 278.597656 391.542969 278.597656 358.566406 C 278.597656 325.59375 305.324219 298.867188 338.300781 298.867188 C 371.273438 298.867188 398 325.59375 398 358.566406 C 398 391.542969 371.273438 418.265625 338.300781 418.265625 Z M 338.300781 418.265625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(89.019608%,29.411765%,52.941176%);fill-opacity:1;" />
<path d="M 497.5 497.867188 L 557.199219 497.867188 L 557.199219 517.765625 L 497.5 517.765625 Z M 497.5 497.867188 " style=" stroke:none;fill-rule:nonzero;fill:rgb(36.862745%,70.196078%,81.960784%);fill-opacity:1;" />
<path d="M 441.074219 298.011719 L 464.953125 274.132812 L 445.351562 254.519531 C 428.050781 279.582031 408.875 303.304688 388 325.484375 C 406.21875 352.964844 398.695312 390.007812 371.214844 408.226562 C 349.113281 422.875 319.992188 421.191406 299.734375 404.089844 C 275.238281 422.394531 249.664062 439.230469 223.167969 454.496094 L 253.882812 485.210938 L 277.765625 461.332031 C 287.347656 466.992188 297.675781 471.28125 308.449219 474.058594 L 308.449219 507.816406 L 368.148438 507.816406 L 368.148438 474.058594 C 378.9375 471.28125 389.265625 467.003906 398.855469 461.339844 L 422.734375 485.222656 L 464.953125 443.003906 L 441.074219 419.121094 C 446.734375 409.53125 451.011719 399.203125 453.789062 388.417969 L 487.550781 388.417969 L 487.550781 328.71875 L 453.789062 328.71875 C 451.011719 317.933594 446.734375 307.601562 441.074219 298.011719 Z M 441.074219 298.011719 " style=" stroke:none;fill-rule:nonzero;fill:rgb(85.098039%,28.235294%,50.588235%);fill-opacity:1;" />
<path d="M 89.550781 119.765625 L 89.550781 468.015625 L 59.699219 468.015625 L 59.699219 149.617188 C 59.699219 133.128906 73.0625 119.765625 89.550781 119.765625 Z M 89.550781 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(24.313725%,54.901961%,78.039216%);fill-opacity:1;" />
<path d="M 39.800781 477.96875 L 59.699219 477.96875 L 59.699219 458.066406 L 39.800781 458.066406 C 28.804688 458.066406 19.898438 449.160156 19.898438 438.167969 L 19.898438 40.167969 C 19.898438 29.171875 28.804688 20.265625 39.800781 20.265625 L 477.597656 20.265625 C 488.59375 20.265625 497.5 29.171875 497.5 40.167969 L 497.5 60.066406 L 517.402344 60.066406 L 517.402344 40.167969 C 517.402344 18.1875 499.578125 0.367188 477.601562 0.367188 L 39.800781 0.367188 C 17.820312 0.367188 0 18.1875 0 40.167969 L 0 438.167969 C 0 460.148438 17.820312 477.96875 39.800781 477.96875 Z M 39.800781 477.96875 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 39.800781 40.167969 L 59.699219 40.167969 L 59.699219 60.066406 L 39.800781 60.066406 Z M 39.800781 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 79.597656 40.167969 L 99.5 40.167969 L 99.5 60.066406 L 79.597656 60.066406 Z M 79.597656 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 119.402344 40.167969 L 139.300781 40.167969 L 139.300781 60.066406 L 119.402344 60.066406 Z M 119.402344 40.167969 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 79.597656 517.765625 C 79.597656 539.746094 97.421875 557.566406 119.398438 557.566406 L 557.199219 557.566406 C 579.179688 557.566406 597 539.746094 597 517.765625 L 597 119.765625 C 597 97.789062 579.179688 79.96875 557.199219 79.96875 L 119.402344 79.96875 C 97.421875 79.96875 79.601562 97.789062 79.601562 119.765625 L 79.601562 517.765625 Z M 99.5 119.765625 C 99.5 108.773438 108.40625 99.867188 119.402344 99.867188 L 557.199219 99.867188 C 568.195312 99.867188 577.101562 108.773438 577.101562 119.765625 L 577.101562 517.765625 C 577.101562 528.761719 568.195312 537.667969 557.199219 537.667969 L 119.402344 537.667969 C 108.40625 537.667969 99.5 528.761719 99.5 517.765625 Z M 99.5 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 119.402344 119.765625 L 139.300781 119.765625 L 139.300781 139.667969 L 119.402344 139.667969 Z M 119.402344 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 159.199219 119.765625 L 179.101562 119.765625 L 179.101562 139.667969 L 159.199219 139.667969 Z M 159.199219 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 199 119.765625 L 218.902344 119.765625 L 218.902344 139.667969 L 199 139.667969 Z M 199 119.765625 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 119.402344 159.566406 L 557.199219 159.566406 L 557.199219 179.46875 L 119.402344 179.46875 Z M 119.402344 159.566406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 119.402344 209.316406 L 199 209.316406 L 199 229.21875 L 119.402344 229.21875 Z M 119.402344 209.316406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 119.402344 249.117188 L 179.101562 249.117188 L 179.101562 269.019531 L 119.402344 269.019531 Z M 119.402344 249.117188 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 338.300781 288.917969 C 299.832031 288.917969 268.648438 320.101562 268.648438 358.566406 C 268.648438 397.035156 299.832031 428.21875 338.300781 428.21875 C 376.765625 428.21875 407.949219 397.035156 407.949219 358.566406 C 407.910156 320.121094 376.746094 288.957031 338.300781 288.917969 Z M 338.300781 408.316406 C 310.828125 408.316406 288.550781 386.039062 288.550781 358.566406 C 288.550781 331.097656 310.828125 308.820312 338.300781 308.820312 C 365.773438 308.820312 388.050781 331.097656 388.050781 358.566406 C 388.019531 386.027344 365.761719 408.289062 338.300781 408.316406 Z M 338.300781 408.316406 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 497.5 328.71875 C 497.5 323.222656 493.042969 318.765625 487.550781 318.765625 L 461.253906 318.765625 C 459.152344 312.230469 456.515625 305.871094 453.382812 299.761719 L 471.980469 281.175781 C 475.859375 277.292969 475.859375 270.988281 471.980469 267.105469 L 429.761719 224.886719 C 425.878906 221.007812 419.570312 221.007812 415.691406 224.886719 L 397.105469 243.476562 C 390.984375 240.351562 384.636719 237.722656 378.097656 235.613281 L 378.097656 209.316406 C 378.097656 203.824219 373.640625 199.367188 368.148438 199.367188 L 308.449219 199.367188 C 302.957031 199.367188 298.5 203.824219 298.5 209.316406 L 298.5 235.613281 C 291.964844 237.722656 285.613281 240.351562 279.496094 243.476562 L 260.910156 224.886719 C 257.027344 221.007812 250.71875 221.007812 246.839844 224.886719 L 204.621094 267.105469 C 200.738281 270.988281 200.738281 277.292969 204.621094 281.175781 L 223.207031 299.761719 C 222.371094 301.394531 221.574219 303.027344 220.820312 304.675781 C 218.71875 309.265625 216.890625 313.972656 215.347656 318.765625 L 189.050781 318.765625 C 183.558594 318.765625 179.097656 323.226562 179.097656 328.71875 L 179.097656 388.417969 C 179.097656 393.910156 183.558594 398.367188 189.050781 398.367188 L 215.347656 398.367188 C 217.445312 404.902344 220.082031 411.261719 223.21875 417.371094 L 204.621094 435.957031 C 200.738281 439.839844 200.738281 446.148438 204.621094 450.027344 L 246.839844 492.246094 C 250.71875 496.128906 257.027344 496.128906 260.910156 492.246094 L 279.496094 473.660156 C 285.613281 476.785156 291.960938 479.410156 298.5 481.519531 L 298.5 507.816406 C 298.5 513.308594 302.957031 517.765625 308.449219 517.765625 L 368.148438 517.765625 C 373.640625 517.765625 378.097656 513.308594 378.097656 507.816406 L 378.097656 481.519531 C 384.636719 479.410156 390.984375 476.78125 397.105469 473.660156 L 415.691406 492.246094 C 419.570312 496.125 425.878906 496.125 429.761719 492.246094 L 471.980469 450.027344 C 475.859375 446.144531 475.859375 439.839844 471.980469 435.957031 L 453.382812 417.371094 C 456.515625 411.261719 459.152344 404.902344 461.253906 398.367188 L 487.550781 398.367188 C 493.042969 398.367188 497.5 393.910156 497.5 388.417969 Z M 477.597656 378.46875 L 453.78125 378.46875 C 449.242188 378.46875 445.28125 381.542969 444.148438 385.929688 C 441.601562 395.8125 437.691406 405.28125 432.507812 414.066406 C 430.199219 417.980469 430.835938 422.945312 434.039062 426.15625 L 450.863281 442.992188 L 422.726562 471.140625 L 405.890625 454.304688 C 402.679688 451.09375 397.703125 450.464844 393.792969 452.773438 C 385.007812 457.957031 375.542969 461.878906 365.664062 464.414062 C 361.277344 465.550781 358.203125 469.507812 358.203125 474.046875 L 358.203125 497.867188 L 318.402344 497.867188 L 318.402344 474.046875 C 318.402344 469.507812 315.328125 465.550781 310.941406 464.414062 C 301.058594 461.878906 291.597656 457.957031 282.8125 452.773438 C 278.902344 450.464844 273.925781 451.09375 270.710938 454.304688 L 253.878906 471.140625 L 225.738281 442.992188 L 242.566406 426.15625 C 245.769531 422.945312 246.40625 417.980469 244.097656 414.066406 C 238.914062 405.28125 235.003906 395.808594 232.457031 385.929688 C 231.320312 381.542969 227.363281 378.46875 222.824219 378.46875 L 199.003906 378.46875 L 199.003906 338.667969 L 222.824219 338.667969 C 227.363281 338.667969 231.320312 335.59375 232.457031 331.207031 C 234.066406 324.945312 236.226562 318.835938 238.921875 312.964844 C 240.472656 309.5625 242.207031 306.242188 244.097656 303.015625 C 246.394531 299.105469 245.769531 294.140625 242.566406 290.9375 L 225.738281 274.101562 L 253.878906 245.953125 L 270.710938 262.789062 C 273.925781 266.003906 278.902344 266.628906 282.8125 264.320312 C 291.597656 259.136719 301.058594 255.21875 310.941406 252.679688 C 315.328125 251.546875 318.402344 247.585938 318.402344 243.046875 L 318.402344 219.269531 L 358.203125 219.269531 L 358.203125 243.089844 C 358.203125 247.625 361.277344 251.585938 365.664062 252.71875 C 375.546875 255.257812 385.007812 259.175781 393.792969 264.363281 C 397.703125 266.667969 402.679688 266.042969 405.890625 262.828125 L 422.726562 245.992188 L 450.863281 274.140625 L 434.039062 290.976562 C 430.835938 294.191406 430.199219 299.15625 432.507812 303.066406 C 437.691406 311.851562 441.601562 321.324219 444.148438 331.203125 C 445.285156 335.59375 449.242188 338.667969 453.78125 338.667969 L 477.601562 338.667969 L 477.601562 378.46875 Z M 477.597656 378.46875 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
<path d="M 497.5 497.867188 L 557.199219 497.867188 L 557.199219 517.765625 L 497.5 517.765625 Z M 497.5 497.867188 " style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

13
wwwroot/js/site.js Normal file
View file

@ -0,0 +1,13 @@
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
function copyToClipboard(text) {
return navigator.clipboard.writeText(text).then(
() => {
return true;
},
() => {
return false;
}
);
}

View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
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 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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
/*! DataTables Bootstrap 5 integration
* 2020 SpryMedia Ltd - datatables.net/license
*/
!function(t){"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return t(e,window,document)}):"object"==typeof exports?module.exports=function(e,a){return e=e||window,(a=a||("undefined"!=typeof window?require("jquery"):require("jquery")(e))).fn.dataTable||require("datatables.net")(e,a),t(a,0,e.document)}:t(jQuery,window,document)}(function(x,e,r,s){"use strict";var o=x.fn.dataTable;return x.extend(!0,o.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row dt-row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",renderer:"bootstrap"}),x.extend(o.ext.classes,{sWrapper:"dataTables_wrapper dt-bootstrap5",sFilterInput:"form-control form-control-sm",sLengthSelect:"form-select form-select-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"}),o.ext.renderer.pageButton.bootstrap=function(i,e,d,a,l,c){function u(e,a){for(var t,n,r=function(e){e.preventDefault(),x(e.currentTarget).hasClass("disabled")||b.page()==e.data.action||b.page(e.data.action).draw("page")},s=0,o=a.length;s<o;s++)if(n=a[s],Array.isArray(n))u(e,n);else{switch(f=p="",n){case"ellipsis":p="&#x2026;",f="disabled";break;case"first":p=m.sFirst,f=n+(0<l?"":" disabled");break;case"previous":p=m.sPrevious,f=n+(0<l?"":" disabled");break;case"next":p=m.sNext,f=n+(l<c-1?"":" disabled");break;case"last":p=m.sLast,f=n+(l<c-1?"":" disabled");break;default:p=n+1,f=l===n?"active":""}p&&(t=x("<li>",{class:g.sPageButton+" "+f,id:0===d&&"string"==typeof n?i.sTableId+"_"+n:null}).append(x("<a>",{href:"#","aria-controls":i.sTableId,"aria-label":w[n],"data-dt-idx":n,tabindex:i.iTabIndex,class:"page-link"}).html(p)).appendTo(e),i.oApi._fnBindAction(t,{action:n},r))}}var p,f,t,b=new o.Api(i),g=i.oClasses,m=i.oLanguage.oPaginate,w=i.oLanguage.oAria.paginate||{},e=x(e);try{t=e.find(r.activeElement).data("dt-idx")}catch(e){}var n=e.children("ul.pagination");n.length?n.empty():n=e.html("<ul/>").children("ul").addClass("pagination"),u(n,a),t!==s&&e.find("[data-dt-idx="+t+"]").trigger("focus")},o});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) .NET Foundation and Contributors
All rights reserved.
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 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.

View file

@ -0,0 +1,435 @@
/**
* @license
* Unobtrusive validation support library for jQuery and jQuery Validate
* Copyright (c) .NET Foundation. All rights reserved.
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
* @version v4.0.0
*/
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
/*global document: false, jQuery: false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports
module.exports = factory(require('jquery-validation'));
} else {
// Browser global
jQuery.validator.unobtrusive = factory(jQuery);
}
}(function ($) {
var $jQval = $.validator,
adapters,
data_validation = "unobtrusiveValidation";
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function splitAndTrim(value) {
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
}
function escapeAttributeValue(value) {
// As mentioned on http://api.jquery.com/category/selectors/
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
}
function onErrors(event, validator) { // 'this' is the form element
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
function onSuccess(error) { // 'this' is the form element
var container = error.data("unobtrusiveContainer");
if (container) {
var replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null;
container.addClass("field-validation-valid").removeClass("field-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container.empty();
}
}
}
function onReset(event) { // 'this' is the form element
var $form = $(this),
key = '__jquery_unobtrusive_validation_form_reset';
if ($form.data(key)) {
return;
}
// Set a flag that indicates we're currently resetting the form.
$form.data(key, true);
try {
$form.data("validator").resetForm();
} finally {
$form.removeData(key);
}
$form.find(".validation-summary-errors")
.addClass("validation-summary-valid")
.removeClass("validation-summary-errors");
$form.find(".field-validation-error")
.addClass("field-validation-valid")
.removeClass("field-validation-error")
.removeData("unobtrusiveContainer")
.find(">*") // If we were using valmsg-replace, get the underlying error
.removeData("unobtrusiveContainer");
}
function validationInfo(form) {
var $form = $(form),
result = $form.data(data_validation),
onResetProxy = $.proxy(onReset, form),
defaultOptions = $jQval.unobtrusive.options || {},
execInContext = function (name, args) {
var func = defaultOptions[name];
func && $.isFunction(func) && func.apply(form, args);
};
if (!result) {
result = {
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
},
attachValidation: function () {
$form
.off("reset." + data_validation, onResetProxy)
.on("reset." + data_validation, onResetProxy)
.validate(this.options);
},
validate: function () { // a validation function that is called by unobtrusive Ajax
$form.validate();
return $form.valid();
}
};
$form.data(data_validation, result);
}
return result;
}
$jQval.unobtrusive = {
adapters: [],
parseElement: function (element, skipAttach) {
/// <summary>
/// Parses a single HTML element for unobtrusive validation attributes.
/// </summary>
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
/// validation to the form. If parsing just this single element, you should specify true.
/// If parsing several elements, you should specify false, and manually attach the validation
/// to the form when you are finished. The default is false.</param>
var $element = $(element),
form = $element.parents("form")[0],
valInfo, rules, messages;
if (!form) { // Cannot do client-side validation without a form
return;
}
valInfo = validationInfo(form);
valInfo.options.rules[element.name] = rules = {};
valInfo.options.messages[element.name] = messages = {};
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name,
message = $element.attr(prefix),
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () {
paramValues[this] = $element.attr(prefix + this);
});
this.adapt({
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
});
$.extend(rules, { "__dummy__": true });
if (!skipAttach) {
valInfo.attachValidation();
}
},
parse: function (selector) {
/// <summary>
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
/// attribute values.
/// </summary>
/// <param name="selector" type="String">Any valid jQuery selector.</param>
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
// element with data-val=true
var $selector = $(selector),
$forms = $selector.parents()
.addBack()
.filter("form")
.add($selector.find("form"))
.has("[data-val=true]");
$selector.find("[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
$forms.each(function () {
var info = validationInfo(this);
if (info) {
info.attachValidation();
}
});
}
};
adapters = $jQval.unobtrusive.adapters;
adapters.add = function (adapterName, params, fn) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
/// mmmm is the parameter name).</param>
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
/// attributes into jQuery Validate rules and/or messages.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
adapters.addBool = function (adapterName, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has no parameter values.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, function (options) {
setValidationValues(options, ruleName || adapterName, true);
});
};
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a minimum value.</param>
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a maximum value.</param>
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
/// have both a minimum and maximum value.</param>
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the minimum value. The default is "min".</param>
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the maximum value. The default is "max".</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
var min = options.params.min,
max = options.params.max;
if (min && max) {
setValidationValues(options, minMaxRuleName, [min, max]);
}
else if (min) {
setValidationValues(options, minRuleName, min);
}
else if (max) {
setValidationValues(options, maxRuleName, max);
}
});
};
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has a single value.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
/// The default is "val".</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [attribute || "val"], function (options) {
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
});
};
$jQval.addMethod("__dummy__", function (value, element, params) {
return true;
});
$jQval.addMethod("regex", function (value, element, params) {
var match;
if (this.optional(element)) {
return true;
}
match = new RegExp(params).exec(value);
return (match && (match.index === 0) && (match[0].length === value.length));
});
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
var match;
if (nonalphamin) {
match = value.match(/\W/g);
match = match && match.length >= nonalphamin;
}
return match;
});
if ($jQval.methods.extension) {
adapters.addSingleVal("accept", "mimtype");
adapters.addSingleVal("extension", "extension");
} else {
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
// validating the extension, and ignore mime-type validations as they are not supported.
adapters.addSingleVal("extension", "extension", "accept");
}
adapters.addSingleVal("regex", "pattern");
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
adapters.add("equalto", ["other"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
setValidationValues(options, "equalTo", element);
});
adapters.add("required", function (options) {
// jQuery Validate equates "required" with "mandatory" for checkbox elements
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
setValidationValues(options, "required", true);
}
});
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
// For checkboxes and radio buttons, only pick up values from checked fields.
if (field.is(":checkbox")) {
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
}
else if (field.is(":radio")) {
return field.filter(":checked").val() || '';
}
return field.val();
};
});
setValidationValues(options, "remote", value);
});
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
if (options.params.min) {
setValidationValues(options, "minlength", options.params.min);
}
if (options.params.nonalphamin) {
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
}
if (options.params.regex) {
setValidationValues(options, "regex", options.params.regex);
}
});
adapters.add("fileextensions", ["extensions"], function (options) {
setValidationValues(options, "extension", options.params.extensions);
});
$(function () {
$jQval.unobtrusive.parse(document);
});
return $jQval.unobtrusive;
}));

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,22 @@
The MIT License (MIT)
=====================
Copyright Jörn Zaefferer
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 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.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
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 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.

10881
wwwroot/lib/jquery/dist/jquery.js vendored Normal file

File diff suppressed because it is too large Load diff

2
wwwroot/lib/jquery/dist/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long