diff --git a/.gitignore b/.gitignore index c2a3f28..a5271cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ riderModule.iml /*.sln.DotSettings.user */appsettings.Secret.yml -*/tempkey.jwk \ No newline at end of file +*/tempkey.jwk +cert diff --git a/OAuthTest/OAuthTest.csproj b/OAuthTest/OAuthTest.csproj index c606b82..96de6d6 100644 --- a/OAuthTest/OAuthTest.csproj +++ b/OAuthTest/OAuthTest.csproj @@ -1,10 +1,10 @@ - net7.0 + net10.0 - + diff --git a/OAuthTest/Startup.cs b/OAuthTest/Startup.cs index 0ec8a6a..2fab532 100644 --- a/OAuthTest/Startup.cs +++ b/OAuthTest/Startup.cs @@ -24,10 +24,10 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpLogging(logging => { logging.LoggingFields = HttpLoggingFields.All; - //Write your code to configure the HttpLogging middleware here + //Write your code to configure the HttpLogging middleware here }); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - + services.AddControllersWithViews(); services.AddAuthentication(options => @@ -39,15 +39,15 @@ public void ConfigureServices(IServiceCollection services) .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; - - options.Authority = "https://localhost:5003"; - options.ClientId = "A"; - options.ClientSecret = "A"; + + options.Authority = "https://localhost:5001"; + options.ClientId = "test_client"; + options.ClientSecret = "test_secret"; options.GetClaimsFromUserInfoEndpoint = true; options.ResponseType = OpenIdConnectResponseType.Code; //options.SaveTokens = true; - + options.Scope.Add("profile"); options.Scope.Add("email"); options.ResponseType = "code"; @@ -85,4 +85,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) pattern: "{controller=Home}/{action=Index}/{id?}"); }); } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 930c780..455c7b7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,13 @@ ConnectionStrings: Mutex: # Change this to something local on disk. DbPath: 'C:\Users\Pieter-Jan Briers\Projects\ss14\web\mutex.db' -``` +``` * Create the mutex DB mentioned above manually, and run `init_mutex.sql` on it. (I recommend https://sqlitebrowser.org/ for this task) * If I didn't forget anything you should now be able to start both services and it should work:tm:. + +### Handling migrations +The following ef core commands can be used: +- Creating a migration: `dotnet ef migrations add -p SS14.Auth.Shared` +- Removing migrations: `dotnet ef migrations remove -p SS14.Auth.Shared --connection ""` +- Updating the database: `dotnet ef database update -p SS14.Auth.Shared -s .\SS14.Web\ --connection ""` diff --git a/SS14.Auth.Shared/Data/AccountLogManager.cs b/SS14.Auth.Shared/Data/AccountLogManager.cs index c067042..a410d2d 100644 --- a/SS14.Auth.Shared/Data/AccountLogManager.cs +++ b/SS14.Auth.Shared/Data/AccountLogManager.cs @@ -88,4 +88,4 @@ public AccountLogActor NoActor() } } -public sealed record AccountLogActor(Guid? User, IPAddress? Address); \ No newline at end of file +public sealed record AccountLogActor(Guid? User, IPAddress? Address); diff --git a/SS14.Auth.Shared/Data/ApplicationDbContext.cs b/SS14.Auth.Shared/Data/ApplicationDbContext.cs index 3b3b507..56e429a 100644 --- a/SS14.Auth.Shared/Data/ApplicationDbContext.cs +++ b/SS14.Auth.Shared/Data/ApplicationDbContext.cs @@ -1,9 +1,4 @@ using System; -using System.Threading.Tasks; -using IdentityServer4.EntityFramework.Entities; -using IdentityServer4.EntityFramework.Extensions; -using IdentityServer4.EntityFramework.Interfaces; -using IdentityServer4.EntityFramework.Options; using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; @@ -11,9 +6,7 @@ namespace SS14.Auth.Shared.Data; public class ApplicationDbContext : IdentityDbContext, - IDataProtectionKeyContext, - IConfigurationDbContext, - IPersistedGrantDbContext + IDataProtectionKeyContext { public ApplicationDbContext(DbContextOptions options) : base(options) @@ -61,10 +54,6 @@ protected override void OnModelCreating(ModelBuilder builder) .HasIndex(p => p.SpaceUserId) .IsUnique(); - builder.Entity() - .HasIndex(p => new { p.ClientId }) - .IsUnique(); - builder.Entity() .HasIndex(h => h.ClientData) .IsUnique(); @@ -72,38 +61,6 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasIndex(h => new { h.HwidId, h.SpaceUserId }) .IsUnique(); - - var cfgStoreOptions = new ConfigurationStoreOptions - { - IdentityResource = new TableConfiguration("IdentityResources", "IS4"), - IdentityResourceClaim = new TableConfiguration("IdentityResourceClaims", "IS4"), - IdentityResourceProperty = new TableConfiguration("IdentityResourceProperties", "IS4"), - ApiResource = new TableConfiguration("ApiResources", "IS4"), - ApiResourceSecret = new TableConfiguration("ApiResourceSecrets", "IS4"), - ApiResourceScope = new TableConfiguration("ApiResourceScopes", "IS4"), - ApiResourceClaim = new TableConfiguration("ApiResourceClaims", "IS4"), - ApiResourceProperty = new TableConfiguration("ApiResourceProperties", "IS4"), - Client = new TableConfiguration("Clients", "IS4"), - ClientGrantType = new TableConfiguration("ClientGrantTypes", "IS4"), - ClientRedirectUri = new TableConfiguration("ClientRedirectUris", "IS4"), - ClientPostLogoutRedirectUri = new TableConfiguration("ClientPostLogoutRedirectUris", "IS4"), - ClientScopes = new TableConfiguration("ClientScopes", "IS4"), - ClientSecret = new TableConfiguration("ClientSecrets", "IS4"), - ClientClaim = new TableConfiguration("ClientClaims", "IS4"), - ClientIdPRestriction = new TableConfiguration("ClientIdPRestrictions", "IS4"), - ClientCorsOrigin = new TableConfiguration("ClientCorsOrigins", "IS4"), - ClientProperty = new TableConfiguration("ClientProperties", "IS4"), - ApiScope = new TableConfiguration("ApiScopes", "IS4"), - ApiScopeClaim = new TableConfiguration("ApiScopeClaims", "IS4"), - ApiScopeProperty = new TableConfiguration("ApiScopeProperties", "IS4") - }; - builder.ConfigureClientContext(cfgStoreOptions); - builder.ConfigureResourcesContext(cfgStoreOptions); - builder.ConfigurePersistedGrantContext(new OperationalStoreOptions - { - PersistedGrants = new TableConfiguration("PersistedGrants", "IS4"), - DeviceFlowCodes = new TableConfiguration("DeviceCodes", "IS4"), - }); } public DbSet ActiveSessions { get; set; } @@ -113,26 +70,8 @@ protected override void OnModelCreating(ModelBuilder builder) public DbSet WhitelistEmails { get; set; } public DbSet Patrons { get; set; } public DbSet PatreonWebhookLogs { get; set; } - public DbSet UserOAuthClients { get; set; } public DbSet PastAccountNames { get; set; } public DbSet AccountLogs { get; set; } public DbSet Hwids { get; set; } public DbSet HwidUsers { get; set; } - - // IS4 configuration. - public DbSet Clients { get; set; } - public DbSet ClientSecrets { get; set; } - public DbSet ClientCorsOrigins { get; set; } - public DbSet IdentityResources { get; set; } - public DbSet ApiResources { get; set; } - public DbSet ApiScopes { get; set; } - - // IS4 operational. - public DbSet PersistedGrants { get; set; } - public DbSet DeviceFlowCodes { get; set; } - - Task IPersistedGrantDbContext.SaveChangesAsync() - { - return base.SaveChangesAsync(); - } } diff --git a/SS14.Auth.Shared/Data/ApplicationDesignTimeDbContextFactory.cs b/SS14.Auth.Shared/Data/ApplicationDesignTimeDbContextFactory.cs index 9c8635f..41d212b 100644 --- a/SS14.Auth.Shared/Data/ApplicationDesignTimeDbContextFactory.cs +++ b/SS14.Auth.Shared/Data/ApplicationDesignTimeDbContextFactory.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using static SS14.Auth.Shared.Data.OpeniddictDefaultTypes; namespace SS14.Auth.Shared.Data; @@ -11,6 +12,7 @@ public ApplicationDbContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseNpgsql("Server=localhost"); + optionsBuilder.UseOpenIddict(); return new ApplicationDbContext(optionsBuilder.Options); } } diff --git a/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.Designer.cs new file mode 100644 index 0000000..5a37830 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.Designer.cs @@ -0,0 +1,953 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250812220730_SwitchToOpenIddict")] + partial class SwitchToOpenIddict + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.UserOAuthClient", b => + { + b.Property("UserOAuthClientId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserOAuthClientId")); + + b.Property("ClientId") + .HasColumnType("integer"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("UserOAuthClientId"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("UserOAuthClients"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.UserOAuthClient", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.cs b/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.cs new file mode 100644 index 0000000..b4131f6 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250812220730_SwitchToOpenIddict.cs @@ -0,0 +1,998 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class SwitchToOpenIddict : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + /*migrationBuilder.DropForeignKey( + name: "FK_UserOAuthClients_Clients_ClientId", + table: "UserOAuthClients"); + + migrationBuilder.DropTable( + name: "ApiResourceClaims", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiResourceProperties", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiResourceScopes", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiResourceSecrets", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiScopeClaims", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiScopeProperties", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientClaims", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientCorsOrigins", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientGrantTypes", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientIdPRestrictions", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientPostLogoutRedirectUris", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientProperties", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientRedirectUris", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientScopes", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ClientSecrets", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "DeviceCodes", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "IdentityResourceClaims", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "IdentityResourceProperties", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "PersistedGrants", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiResources", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "ApiScopes", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "Clients", + schema: "IS4"); + + migrationBuilder.DropTable( + name: "IdentityResources", + schema: "IS4");*/ + + migrationBuilder.CreateTable( + name: "OpenIddictApplications", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + SpaceUserId = table.Column(type: "uuid", nullable: true), + LogoUri = table.Column(type: "text", nullable: true), + ApplicationType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ClientId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ClientSecret = table.Column(type: "text", nullable: true), + ClientType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ConsentType = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + DisplayName = table.Column(type: "text", nullable: true), + DisplayNames = table.Column(type: "text", nullable: true), + JsonWebKeySet = table.Column(type: "text", nullable: true), + Permissions = table.Column(type: "text", nullable: true), + PostLogoutRedirectUris = table.Column(type: "text", nullable: true), + Properties = table.Column(type: "text", nullable: true), + RedirectUris = table.Column(type: "text", nullable: true), + Requirements = table.Column(type: "text", nullable: true), + Settings = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictApplications", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictApplications_AspNetUsers_SpaceUserId", + column: x => x.SpaceUserId, + principalTable: "AspNetUsers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictScopes", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Description = table.Column(type: "text", nullable: true), + Descriptions = table.Column(type: "text", nullable: true), + DisplayName = table.Column(type: "text", nullable: true), + DisplayNames = table.Column(type: "text", nullable: true), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Properties = table.Column(type: "text", nullable: true), + Resources = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictAuthorizations", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ApplicationId = table.Column(type: "text", nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: true), + Properties = table.Column(type: "text", nullable: true), + Scopes = table.Column(type: "text", nullable: true), + Status = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "character varying(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "character varying(50)", maxLength: 50, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictAuthorizations_OpenIddictApplications_Application~", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "OpenIddictTokens", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + ApplicationId = table.Column(type: "text", nullable: true), + AuthorizationId = table.Column(type: "text", nullable: true), + ConcurrencyToken = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: true), + ExpirationDate = table.Column(type: "timestamp with time zone", nullable: true), + Payload = table.Column(type: "text", nullable: true), + Properties = table.Column(type: "text", nullable: true), + RedemptionDate = table.Column(type: "timestamp with time zone", nullable: true), + ReferenceId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Status = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Subject = table.Column(type: "character varying(400)", maxLength: 400, nullable: true), + Type = table.Column(type: "character varying(150)", maxLength: 150, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_OpenIddictTokens", x => x.Id); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId", + column: x => x.ApplicationId, + principalTable: "OpenIddictApplications", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId", + column: x => x.AuthorizationId, + principalTable: "OpenIddictAuthorizations", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictApplications_ClientId", + table: "OpenIddictApplications", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictApplications_SpaceUserId", + table: "OpenIddictApplications", + column: "SpaceUserId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type", + table: "OpenIddictAuthorizations", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictScopes_Name", + table: "OpenIddictScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type", + table: "OpenIddictTokens", + columns: new[] { "ApplicationId", "Status", "Subject", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_AuthorizationId", + table: "OpenIddictTokens", + column: "AuthorizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OpenIddictTokens_ReferenceId", + table: "OpenIddictTokens", + column: "ReferenceId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OpenIddictScopes"); + + migrationBuilder.DropTable( + name: "OpenIddictTokens"); + + migrationBuilder.DropTable( + name: "OpenIddictAuthorizations"); + + migrationBuilder.DropTable( + name: "OpenIddictApplications"); + + /*migrationBuilder.EnsureSchema( + name: "IS4"); + + migrationBuilder.CreateTable( + name: "ApiResources", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AllowedAccessTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Created = table.Column(type: "timestamp with time zone", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Enabled = table.Column(type: "boolean", nullable: false), + LastAccessed = table.Column(type: "timestamp with time zone", nullable: true), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + NonEditable = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Updated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiScopes", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Emphasize = table.Column(type: "boolean", nullable: false), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Required = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clients", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AbsoluteRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + AccessTokenLifetime = table.Column(type: "integer", nullable: false), + AccessTokenType = table.Column(type: "integer", nullable: false), + AllowAccessTokensViaBrowser = table.Column(type: "boolean", nullable: false), + AllowOfflineAccess = table.Column(type: "boolean", nullable: false), + AllowPlainTextPkce = table.Column(type: "boolean", nullable: false), + AllowRememberConsent = table.Column(type: "boolean", nullable: false), + AllowedIdentityTokenSigningAlgorithms = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + AlwaysIncludeUserClaimsInIdToken = table.Column(type: "boolean", nullable: false), + AlwaysSendClientClaims = table.Column(type: "boolean", nullable: false), + AuthorizationCodeLifetime = table.Column(type: "integer", nullable: false), + BackChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + BackChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + ClientClaimsPrefix = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + ClientUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + ConsentLifetime = table.Column(type: "integer", nullable: true), + Created = table.Column(type: "timestamp with time zone", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + DeviceCodeLifetime = table.Column(type: "integer", nullable: false), + EnableLocalLogin = table.Column(type: "boolean", nullable: false), + Enabled = table.Column(type: "boolean", nullable: false), + FrontChannelLogoutSessionRequired = table.Column(type: "boolean", nullable: false), + FrontChannelLogoutUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + IdentityTokenLifetime = table.Column(type: "integer", nullable: false), + IncludeJwtId = table.Column(type: "boolean", nullable: false), + LastAccessed = table.Column(type: "timestamp with time zone", nullable: true), + LogoUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + NonEditable = table.Column(type: "boolean", nullable: false), + PairWiseSubjectSalt = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + ProtocolType = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + RefreshTokenExpiration = table.Column(type: "integer", nullable: false), + RefreshTokenUsage = table.Column(type: "integer", nullable: false), + RequireClientSecret = table.Column(type: "boolean", nullable: false), + RequireConsent = table.Column(type: "boolean", nullable: false), + RequirePkce = table.Column(type: "boolean", nullable: false), + RequireRequestObject = table.Column(type: "boolean", nullable: false), + SlidingRefreshTokenLifetime = table.Column(type: "integer", nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(type: "boolean", nullable: false), + Updated = table.Column(type: "timestamp with time zone", nullable: true), + UserCodeType = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + UserSsoLifetime = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceCodes", + schema: "IS4", + columns: table => new + { + UserCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + DeviceCode = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Expiration = table.Column(type: "timestamp with time zone", nullable: false), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "IdentityResources", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Created = table.Column(type: "timestamp with time zone", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + DisplayName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Emphasize = table.Column(type: "boolean", nullable: false), + Enabled = table.Column(type: "boolean", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + NonEditable = table.Column(type: "boolean", nullable: false), + Required = table.Column(type: "boolean", nullable: false), + ShowInDiscoveryDocument = table.Column(type: "boolean", nullable: false), + Updated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + schema: "IS4", + columns: table => new + { + Key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ClientId = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ConsumedTime = table.Column(type: "timestamp with time zone", nullable: true), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false), + Data = table.Column(type: "character varying(50000)", maxLength: 50000, nullable: false), + Description = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Expiration = table.Column(type: "timestamp with time zone", nullable: true), + SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + SubjectId = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Type = table.Column(type: "character varying(50)", maxLength: 50, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceClaims", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceClaims_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalSchema: "IS4", + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceProperties", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceProperties_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalSchema: "IS4", + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceScopes", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceScopes", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceScopes_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalSchema: "IS4", + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiResourceSecrets", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ApiResourceId = table.Column(type: "integer", nullable: false), + Created = table.Column(type: "timestamp with time zone", nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + Expiration = table.Column(type: "timestamp with time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalSchema: "IS4", + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalSchema: "IS4", + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeProperties", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ScopeId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeProperties", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeProperties_ApiScopes_ScopeId", + column: x => x.ScopeId, + principalSchema: "IS4", + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientClaims", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientClaims", x => x.Id); + table.ForeignKey( + name: "FK_ClientClaims_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientCorsOrigins", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Origin = table.Column(type: "character varying(150)", maxLength: 150, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); + table.ForeignKey( + name: "FK_ClientCorsOrigins_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientGrantTypes", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + GrantType = table.Column(type: "character varying(250)", maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); + table.ForeignKey( + name: "FK_ClientGrantTypes_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientIdPRestrictions", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Provider = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); + table.ForeignKey( + name: "FK_ClientIdPRestrictions_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientPostLogoutRedirectUris", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + PostLogoutRedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientProperties", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientProperties", x => x.Id); + table.ForeignKey( + name: "FK_ClientProperties_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientRedirectUris", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + RedirectUri = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientScopes", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Scope = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientScopes", x => x.Id); + table.ForeignKey( + name: "FK_ClientScopes_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientSecrets", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClientId = table.Column(type: "integer", nullable: false), + Created = table.Column(type: "timestamp with time zone", nullable: false), + Description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + Expiration = table.Column(type: "timestamp with time zone", nullable: true), + Type = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(4000)", maxLength: 4000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ClientSecrets_Clients_ClientId", + column: x => x.ClientId, + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceClaims", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IdentityResourceId = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "character varying(200)", maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalSchema: "IS4", + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityResourceProperties", + schema: "IS4", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + IdentityResourceId = table.Column(type: "integer", nullable: false), + Key = table.Column(type: "character varying(250)", maxLength: 250, nullable: false), + Value = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id); + table.ForeignKey( + name: "FK_IdentityResourceProperties_IdentityResources_IdentityResour~", + column: x => x.IdentityResourceId, + principalSchema: "IS4", + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceClaims_ApiResourceId", + schema: "IS4", + table: "ApiResourceClaims", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceProperties_ApiResourceId", + schema: "IS4", + table: "ApiResourceProperties", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResources_Name", + schema: "IS4", + table: "ApiResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceScopes_ApiResourceId", + schema: "IS4", + table: "ApiResourceScopes", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiResourceSecrets_ApiResourceId", + schema: "IS4", + table: "ApiResourceSecrets", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ScopeId", + schema: "IS4", + table: "ApiScopeClaims", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeProperties_ScopeId", + schema: "IS4", + table: "ApiScopeProperties", + column: "ScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_Name", + schema: "IS4", + table: "ApiScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientClaims_ClientId", + schema: "IS4", + table: "ClientClaims", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientCorsOrigins_ClientId", + schema: "IS4", + table: "ClientCorsOrigins", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientGrantTypes_ClientId", + schema: "IS4", + table: "ClientGrantTypes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientIdPRestrictions_ClientId", + schema: "IS4", + table: "ClientIdPRestrictions", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientPostLogoutRedirectUris_ClientId", + schema: "IS4", + table: "ClientPostLogoutRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientProperties_ClientId", + schema: "IS4", + table: "ClientProperties", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientRedirectUris_ClientId", + schema: "IS4", + table: "ClientRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_Clients_ClientId", + schema: "IS4", + table: "Clients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientScopes_ClientId", + schema: "IS4", + table: "ClientScopes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientSecrets_ClientId", + schema: "IS4", + table: "ClientSecrets", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + schema: "IS4", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + schema: "IS4", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceClaims_IdentityResourceId", + schema: "IS4", + table: "IdentityResourceClaims", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResourceProperties_IdentityResourceId", + schema: "IS4", + table: "IdentityResourceProperties", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResources_Name", + schema: "IS4", + table: "IdentityResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + schema: "IS4", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + schema: "IS4", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_SessionId_Type", + schema: "IS4", + table: "PersistedGrants", + columns: new[] { "SubjectId", "SessionId", "Type" }); + + migrationBuilder.AddForeignKey( + name: "FK_UserOAuthClients_Clients_ClientId", + table: "UserOAuthClients", + column: "ClientId", + principalSchema: "IS4", + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade);*/ + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.Designer.cs new file mode 100644 index 0000000..95fbed8 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.Designer.cs @@ -0,0 +1,921 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250814194410_AddSpaceAppHomePageProperty")] + partial class AddSpaceAppHomePageProperty + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.cs b/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.cs new file mode 100644 index 0000000..b91ca8e --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814194410_AddSpaceAppHomePageProperty.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class AddSpaceAppHomePageProperty : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserOAuthClients"); + + migrationBuilder.AddColumn( + name: "WebsiteUrl", + table: "OpenIddictApplications", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "WebsiteUrl", + table: "OpenIddictApplications"); + + migrationBuilder.CreateTable( + name: "UserOAuthClients", + columns: table => new + { + UserOAuthClientId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SpaceUserId = table.Column(type: "uuid", nullable: false), + ClientId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserOAuthClients", x => x.UserOAuthClientId); + table.ForeignKey( + name: "FK_UserOAuthClients_AspNetUsers_SpaceUserId", + column: x => x.SpaceUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserOAuthClients_ClientId", + table: "UserOAuthClients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserOAuthClients_SpaceUserId", + table: "UserOAuthClients", + column: "SpaceUserId"); + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.Designer.cs new file mode 100644 index 0000000..7ccadbc --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.Designer.cs @@ -0,0 +1,924 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250814195916_AddSpaceAppClientSecretDescription")] + partial class AddSpaceAppClientSecretDescription + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientSecretDescription") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.cs b/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.cs new file mode 100644 index 0000000..b3e9df0 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814195916_AddSpaceAppClientSecretDescription.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class AddSpaceAppClientSecretDescription : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ClientSecretDescription", + table: "OpenIddictApplications", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ClientSecretDescription", + table: "OpenIddictApplications"); + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.Designer.cs new file mode 100644 index 0000000..314ad7a --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.Designer.cs @@ -0,0 +1,927 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250814215250_AddSpaceAppClientSecretCreationDate")] + partial class AddSpaceAppClientSecretCreationDate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientSecretDescription") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("SecretCreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.cs b/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.cs new file mode 100644 index 0000000..ca20dd2 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250814215250_AddSpaceAppClientSecretCreationDate.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class AddSpaceAppClientSecretCreationDate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecretCreationDate", + table: "OpenIddictApplications", + type: "timestamp with time zone", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SecretCreationDate", + table: "OpenIddictApplications"); + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.Designer.cs new file mode 100644 index 0000000..2bb92a5 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.Designer.cs @@ -0,0 +1,927 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250815165542_RemoveSpaceAppSecretDescriptionProperty")] + partial class RemoveSpaceAppSecretDescriptionProperty + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientSecretDescription") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("SecretCreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.cs b/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.cs new file mode 100644 index 0000000..b950e31 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250815165542_RemoveSpaceAppSecretDescriptionProperty.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class RemoveSpaceAppSecretDescriptionProperty : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.Designer.cs b/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.Designer.cs new file mode 100644 index 0000000..11539c0 --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.Designer.cs @@ -0,0 +1,921 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SS14.Auth.Shared.Data; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250817184858_RemoveSecretDescriptionAndDate")] + partial class RemoveSecretDescriptionAndDate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Actor") + .HasColumnType("uuid"); + + b.Property("ActorAddress") + .HasColumnType("inet"); + + b.Property("Data") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("AccountLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.Property("AuthHashId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("AuthHashId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("AuthHashId"); + + b.HasIndex("HwidId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Hash", "SpaceUserId") + .IsUnique(); + + b.ToTable("AuthHashes"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.BurnerEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("BurnerEmails"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientData") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("TypeCode") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id"); + + b.HasIndex("ClientData") + .IsUnique(); + + b.ToTable("Hwids"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeen") + .HasColumnType("timestamp with time zone"); + + b.Property("HwidId") + .HasColumnType("bigint"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeTime") + .HasColumnType("timestamp with time zone"); + + b.Property("PastName") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.ToTable("PastAccountNames"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PatreonWebhookLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .HasColumnType("jsonb"); + + b.Property("Time") + .HasColumnType("timestamp with time zone"); + + b.Property("Trigger") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("PatreonWebhookLogs"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentTier") + .HasColumnType("text"); + + b.Property("PatreonId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("PatreonId") + .IsUnique(); + + b.HasIndex("SpaceUserId") + .IsUnique(); + + b.ToTable("Patrons"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminLocked") + .HasColumnType("boolean"); + + b.Property("AdminNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastUsernameChange") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Domain") + .IsUnique(); + + b.ToTable("WhitelistEmails"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AccountLog", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AccountLogs") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.AuthHash", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany() + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("AuthHashes") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.HwidUser", b => + { + b.HasOne("SS14.Auth.Shared.Data.Hwid", "Hwid") + .WithMany("Users") + .HasForeignKey("HwidId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Hwid"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("LoginSessions") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany("PastAccountNames") + .HasForeignKey("SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Patron", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithOne("Patron") + .HasForeignKey("SS14.Auth.Shared.Data.Patron", "SpaceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") + .WithMany() + .HasForeignKey("SpaceUserId"); + + b.Navigation("SpaceUser"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => + { + b.Navigation("AccountLogs"); + + b.Navigation("AuthHashes"); + + b.Navigation("LoginSessions"); + + b.Navigation("PastAccountNames"); + + b.Navigation("Patron"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.cs b/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.cs new file mode 100644 index 0000000..94b4caf --- /dev/null +++ b/SS14.Auth.Shared/Data/Migrations/20250817184858_RemoveSecretDescriptionAndDate.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SS14.Auth.Shared.Data.Migrations +{ + /// + public partial class RemoveSecretDescriptionAndDate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ClientSecretDescription", + table: "OpenIddictApplications"); + + migrationBuilder.DropColumn( + name: "SecretCreationDate", + table: "OpenIddictApplications"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ClientSecretDescription", + table: "OpenIddictApplications", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "SecretCreationDate", + table: "OpenIddictApplications", + type: "timestamp with time zone", + nullable: true); + } + } +} diff --git a/SS14.Auth.Shared/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/SS14.Auth.Shared/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 692c7df..f48e1b6 100644 --- a/SS14.Auth.Shared/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/SS14.Auth.Shared/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -19,861 +19,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("ProductVersion", "9.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiResources", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceClaims", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceProperties", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceScopes", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.ToTable("ApiResourceSecrets", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("ApiScopes", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeClaims", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.ToTable("ApiScopeProperties", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ClientClaimsPrefix") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProtocolType") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.ToTable("Clients", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Origin") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientCorsOrigins", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientIdPRestrictions", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientPostLogoutRedirectUris", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientProperties", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(4000) - .HasColumnType("character varying(4000)"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientSecrets", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("character varying(50000)"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("DeviceCode") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Expiration") - .IsRequired() - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("UserCode"); - - b.HasIndex("DeviceCode") - .IsUnique(); - - b.HasIndex("Expiration"); - - b.ToTable("DeviceCodes", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.ToTable("IdentityResources", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("character varying(250)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties", "IS4"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClientId") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasMaxLength(50000) - .HasColumnType("character varying(50000)"); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("SubjectId") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.HasKey("Key"); - - b.HasIndex("Expiration"); - - b.HasIndex("SubjectId", "ClientId", "Type"); - - b.HasIndex("SubjectId", "SessionId", "Type"); - - b.ToTable("PersistedGrants", "IS4"); - }); - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => { b.Property("Id") @@ -1126,42 +276,185 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SpaceUserId") .HasColumnType("uuid"); - b.HasKey("Id"); + b.HasKey("Id"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("HwidId", "SpaceUserId") + .IsUnique(); + + b.ToTable("HwidUsers"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => + { + b.Property("LoginSessionId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + + b.Property("Expires") + .HasColumnType("timestamp with time zone"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("Token") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("LoginSessionId"); + + b.HasIndex("SpaceUserId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("ActiveSessions"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); - b.HasIndex("SpaceUserId"); + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); - b.HasIndex("HwidId", "SpaceUserId") - .IsUnique(); + b.Property("Payload") + .HasColumnType("text"); - b.ToTable("HwidUsers"); - }); + b.Property("Properties") + .HasColumnType("text"); - modelBuilder.Entity("SS14.Auth.Shared.Data.LoginSession", b => - { - b.Property("LoginSessionId") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("LoginSessionId")); + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); - b.Property("Expires") - .HasColumnType("timestamp with time zone"); + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); - b.Property("SpaceUserId") - .HasColumnType("uuid"); + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); - b.Property("Token") - .IsRequired() - .HasColumnType("bytea"); + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); - b.HasKey("LoginSessionId"); + b.HasKey("Id"); - b.HasIndex("SpaceUserId"); + b.HasIndex("AuthorizationId"); - b.HasIndex("Token") + b.HasIndex("ReferenceId") .IsUnique(); - b.ToTable("ActiveSessions"); + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); }); modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => @@ -1240,6 +533,82 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Patrons"); }); + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("LogoUri") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.Property("SpaceUserId") + .HasColumnType("uuid"); + + b.Property("WebsiteUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SpaceUserId"); + + b.ToTable("OpenIddictApplications", (string)null); + }); + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceRole", b => { b.Property("Id") @@ -1330,30 +699,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AspNetUsers", (string)null); }); - modelBuilder.Entity("SS14.Auth.Shared.Data.UserOAuthClient", b => - { - b.Property("UserOAuthClientId") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserOAuthClientId")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("SpaceUserId") - .HasColumnType("uuid"); - - b.HasKey("UserOAuthClientId"); - - b.HasIndex("ClientId") - .IsUnique(); - - b.HasIndex("SpaceUserId"); - - b.ToTable("UserOAuthClients"); - }); - modelBuilder.Entity("SS14.Auth.Shared.Data.WhitelistEmail", b => { b.Property("Id") @@ -1373,193 +718,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WhitelistEmails"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope") - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("SS14.Auth.Shared.Data.SpaceRole", null) @@ -1670,6 +828,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("SpaceUser"); }); + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultToken", b => + { + b.HasOne("SS14.Auth.Shared.Data.SpaceApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + modelBuilder.Entity("SS14.Auth.Shared.Data.PastAccountName", b => { b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") @@ -1692,74 +874,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("SpaceUser"); }); - modelBuilder.Entity("SS14.Auth.Shared.Data.UserOAuthClient", b => + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => { - b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.HasOne("SS14.Auth.Shared.Data.SpaceUser", "SpaceUser") .WithMany() - .HasForeignKey("SpaceUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); + .HasForeignKey("SpaceUserId"); b.Navigation("SpaceUser"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); + b.Navigation("Users"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + modelBuilder.Entity("SS14.Auth.Shared.Data.OpeniddictDefaultTypes+DefaultAuthorization", b => { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); + b.Navigation("Tokens"); }); - modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceApplication", b => { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); + b.Navigation("Authorizations"); - modelBuilder.Entity("SS14.Auth.Shared.Data.Hwid", b => - { - b.Navigation("Users"); + b.Navigation("Tokens"); }); modelBuilder.Entity("SS14.Auth.Shared.Data.SpaceUser", b => diff --git a/SS14.Auth.Shared/Data/OpeniddictDefaultTypes.cs b/SS14.Auth.Shared/Data/OpeniddictDefaultTypes.cs new file mode 100644 index 0000000..c20df5d --- /dev/null +++ b/SS14.Auth.Shared/Data/OpeniddictDefaultTypes.cs @@ -0,0 +1,11 @@ +using OpenIddict.EntityFrameworkCore.Models; + +namespace SS14.Auth.Shared.Data; + +public class OpeniddictDefaultTypes +{ + public sealed class DefaultAuthorization : OpenIddictEntityFrameworkCoreAuthorization; + public sealed class DefaultScope : OpenIddictEntityFrameworkCoreScope; + public sealed class DefaultToken : OpenIddictEntityFrameworkCoreToken; + +} diff --git a/SS14.Auth.Shared/Data/SpaceApplication.cs b/SS14.Auth.Shared/Data/SpaceApplication.cs new file mode 100644 index 0000000..5f7150c --- /dev/null +++ b/SS14.Auth.Shared/Data/SpaceApplication.cs @@ -0,0 +1,15 @@ +#nullable enable +using System; +using JetBrains.Annotations; +using OpenIddict.EntityFrameworkCore.Models; +using static SS14.Auth.Shared.Data.OpeniddictDefaultTypes; + +namespace SS14.Auth.Shared.Data; + +public sealed class SpaceApplication : OpenIddictEntityFrameworkCoreApplication +{ + public Guid? SpaceUserId { get; set; } + public SpaceUser? SpaceUser { get; set; } + public string? LogoUri { get; set; } + public string? WebsiteUrl { get; set; } +} diff --git a/SS14.Auth.Shared/Data/SpaceUserManager.cs b/SS14.Auth.Shared/Data/SpaceUserManager.cs index 66c6d75..6a02c86 100644 --- a/SS14.Auth.Shared/Data/SpaceUserManager.cs +++ b/SS14.Auth.Shared/Data/SpaceUserManager.cs @@ -41,6 +41,7 @@ public override async Task CreateAsync(SpaceUser user) // Whoopsie. var accountLogManager = _services.GetRequiredService(); await accountLogManager.LogAndSave(user, new AccountLogCreated(), accountLogManager.NoActor()); + return result; } @@ -54,4 +55,4 @@ public async Task FindByNameOrEmailAsync(string nameOrEmail) return await FindByEmailAsync(nameOrEmail); } -} \ No newline at end of file +} diff --git a/SS14.Auth.Shared/Data/UserOAuthClient.cs b/SS14.Auth.Shared/Data/UserOAuthClient.cs deleted file mode 100644 index 79258b6..0000000 --- a/SS14.Auth.Shared/Data/UserOAuthClient.cs +++ /dev/null @@ -1,16 +0,0 @@ - -using System; -using IdentityServer4.EntityFramework.Entities; - -namespace SS14.Auth.Shared.Data; - -public sealed class UserOAuthClient -{ - public int UserOAuthClientId { get; set; } - - public Guid SpaceUserId { get; set; } - public int ClientId { get; set; } - - public SpaceUser SpaceUser { get; set; } - public Client Client { get; set; } -} \ No newline at end of file diff --git a/SS14.Auth.Shared/SS14.Auth.Shared.csproj b/SS14.Auth.Shared/SS14.Auth.Shared.csproj index 6acfab5..2ccb398 100644 --- a/SS14.Auth.Shared/SS14.Auth.Shared.csproj +++ b/SS14.Auth.Shared/SS14.Auth.Shared.csproj @@ -1,33 +1,33 @@ - net6.0 + net10.0 Library 12 - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + - - - - + + + + - - - + + diff --git a/SS14.Auth.Shared/StartupHelpers.cs b/SS14.Auth.Shared/StartupHelpers.cs index b8594bc..a98d43c 100644 --- a/SS14.Auth.Shared/StartupHelpers.cs +++ b/SS14.Auth.Shared/StartupHelpers.cs @@ -1,6 +1,8 @@ using System; using System.IO; using System.Security.Cryptography; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -16,11 +18,17 @@ using SS14.Auth.Shared.Emails; using SS14.Auth.Shared.MutexDb; using SS14.Auth.Shared.Sessions; +using static SS14.Auth.Shared.Data.OpeniddictDefaultTypes; namespace SS14.Auth.Shared; public static class StartupHelpers { + public static void AddShared(this WebApplicationBuilder builder) + { + AddShared(builder.Services, builder.Configuration); + } + public static void AddShared(IServiceCollection services, IConfiguration config) { // Configure. @@ -34,10 +42,12 @@ public static void AddShared(IServiceCollection services, IConfiguration config) // The fact that this isn't default absolutely baffles me. options.ValidationInterval = TimeSpan.FromSeconds(5); }); - + services.AddDbContext(options => - options.UseNpgsql( - config.GetConnectionString("DefaultConnection"))); + { + options.UseNpgsql(config.GetConnectionString("DefaultConnection")); + options.UseOpenIddict(); + }); services.AddDataProtection() .PersistKeysToDbContext() @@ -86,7 +96,7 @@ public static void AddShared(IServiceCollection services, IConfiguration config) services.AddTransient(); } - + public static void SetupLoki(LoggerConfiguration log, IConfiguration cfg, string appName) { var dat = cfg.GetSection("Serilog:Loki").Get(); @@ -123,4 +133,4 @@ private sealed class LokiConfigurationData public string Username { get; set; } public string Password { get; set; } } -} \ No newline at end of file +} diff --git a/SS14.Auth/SS14.Auth.csproj b/SS14.Auth/SS14.Auth.csproj index cd1b760..1f35664 100644 --- a/SS14.Auth/SS14.Auth.csproj +++ b/SS14.Auth/SS14.Auth.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable @@ -10,13 +10,13 @@ - - + + - - - + + + diff --git a/SS14.ServerHub.Shared/CommunityMatcher.cs b/SS14.ServerHub.Shared/CommunityMatcher.cs index 7197081..d9b13b7 100644 --- a/SS14.ServerHub.Shared/CommunityMatcher.cs +++ b/SS14.ServerHub.Shared/CommunityMatcher.cs @@ -2,6 +2,7 @@ using System.Net.Sockets; using DnsClient; using Microsoft.EntityFrameworkCore; +using NpgsqlTypes; using SS14.ServerHub.Shared.Data; namespace SS14.ServerHub.Shared; @@ -16,7 +17,7 @@ public static IQueryable CheckIP(HubDbContext dbContext return dbContext.TrackedCommunityAddress .Where(c => EF.Functions.ContainsOrEqual(c.Address, address)); } - + public static IQueryable CheckDomain(HubDbContext dbContext, string domain) { return dbContext.TrackedCommunityDomain @@ -158,7 +159,7 @@ public sealed class FailedResolveException : Exception { public FailedResolveException(string message, Exception e) : base(message, e) { - + } } -} \ No newline at end of file +} diff --git a/SS14.ServerHub.Shared/Data/HubDbContext.cs b/SS14.ServerHub.Shared/Data/HubDbContext.cs index 89cfe75..b941660 100644 --- a/SS14.ServerHub.Shared/Data/HubDbContext.cs +++ b/SS14.ServerHub.Shared/Data/HubDbContext.cs @@ -9,13 +9,6 @@ public HubDbContext(DbContextOptions options) : base(options) { } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - - optionsBuilder.ReplaceService(); - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -51,4 +44,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet ServerStatusArchive { get; set; } = default!; public DbSet UniqueServerName { get; set; } = default!; public DbSet HubAudit { get; set; } = default!; -} \ No newline at end of file +} diff --git a/SS14.ServerHub.Shared/Data/NpgsqlTypeMapping.cs b/SS14.ServerHub.Shared/Data/NpgsqlTypeMapping.cs deleted file mode 100644 index 8b86586..0000000 --- a/SS14.ServerHub.Shared/Data/NpgsqlTypeMapping.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Linq.Expressions; -using System.Net; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Storage; -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; -using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; - -namespace SS14.ServerHub.Shared.Data; - -#pragma warning disable EF1001 - -// Taken from https://github.com/npgsql/efcore.pg/issues/1158 -// To support inet -> (IPAddress, int) mapping. -public class CustomNpgsqlTypeMappingSource : NpgsqlTypeMappingSource -{ - public CustomNpgsqlTypeMappingSource( - TypeMappingSourceDependencies dependencies, - RelationalTypeMappingSourceDependencies relationalDependencies, - ISqlGenerationHelper sqlGenerationHelper, - INpgsqlOptions? npgsqlOptions = null) - : base(dependencies, relationalDependencies, sqlGenerationHelper, npgsqlOptions) - { - StoreTypeMappings["inet"] = - new RelationalTypeMapping[] - { - new NpgsqlInetWithMaskTypeMapping(), - new NpgsqlInetTypeMapping() - }; - } -} - -// Basically copied from NpgsqlCidrTypeMapping -public class NpgsqlInetWithMaskTypeMapping : NpgsqlTypeMapping -{ - public NpgsqlInetWithMaskTypeMapping() : base("inet", typeof((IPAddress, int)), NpgsqlTypes.NpgsqlDbType.Inet) - { - } - - protected NpgsqlInetWithMaskTypeMapping(RelationalTypeMappingParameters parameters) - : base(parameters, NpgsqlTypes.NpgsqlDbType.Inet) - { - } - - protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - => new NpgsqlInetWithMaskTypeMapping(parameters); - - protected override string GenerateNonNullSqlLiteral(object value) - { - var cidr = ((IPAddress Address, int Subnet))value; - return $"INET '{cidr.Address}/{cidr.Subnet}'"; - } - - public override Expression GenerateCodeLiteral(object value) - { - var cidr = ((IPAddress Address, int Subnet))value; - return Expression.New( - Constructor, - Expression.Call(ParseMethod, Expression.Constant(cidr.Address.ToString())), - Expression.Constant(cidr.Subnet)); - } - - static readonly MethodInfo ParseMethod = typeof(IPAddress).GetMethod("Parse", new[] { typeof(string) })!; - - static readonly ConstructorInfo Constructor = - typeof((IPAddress, int)).GetConstructor(new[] { typeof(IPAddress), typeof(int) })!; -} \ No newline at end of file diff --git a/SS14.ServerHub.Shared/Data/TrackedCommunityAddress.cs b/SS14.ServerHub.Shared/Data/TrackedCommunityAddress.cs index 4fb7cf4..4ee0478 100644 --- a/SS14.ServerHub.Shared/Data/TrackedCommunityAddress.cs +++ b/SS14.ServerHub.Shared/Data/TrackedCommunityAddress.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Net; +using NpgsqlTypes; namespace SS14.ServerHub.Shared.Data; @@ -12,18 +13,18 @@ public class TrackedCommunityAddress /// The ID of this entity in the database. /// public int Id { get; set; } - + /// /// The address range in question. /// [Column(TypeName = "inet")] - public (IPAddress, int) Address { get; set; } + public NpgsqlCidr Address { get; set; } /// /// The ID of the this address belongs to. /// public int TrackedCommunityId { get; set; } - + // Navigation properties public TrackedCommunity TrackedCommunity { get; set; } = default!; -} \ No newline at end of file +} diff --git a/SS14.ServerHub.Shared/Helpers/IPHelper.cs b/SS14.ServerHub.Shared/Helpers/IPHelper.cs index a0909ae..3e16604 100644 --- a/SS14.ServerHub.Shared/Helpers/IPHelper.cs +++ b/SS14.ServerHub.Shared/Helpers/IPHelper.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using NpgsqlTypes; namespace SS14.ServerHub.Shared.Helpers; @@ -8,15 +9,15 @@ namespace SS14.ServerHub.Shared.Helpers; /// public static class IPHelper { - public static bool TryParseIpOrCidr(string str, out (IPAddress, int) cidr) + public static bool TryParseIpOrCidr(string str, out NpgsqlCidr cidr) { if (IPAddress.TryParse(str, out var addr)) { - cidr = (addr, addr.AddressFamily switch + cidr = new NpgsqlCidr(addr, addr.AddressFamily switch { AddressFamily.InterNetwork => 32, AddressFamily.InterNetworkV6 => 128, - _ => throw new ArgumentException(nameof(str)) + _ => throw new ArgumentException(null, nameof(str)), }); return true; } @@ -24,7 +25,7 @@ public static bool TryParseIpOrCidr(string str, out (IPAddress, int) cidr) return TryParseCidr(str, out cidr); } - public static bool TryParseCidr(string str, out (IPAddress, int) cidr) + public static bool TryParseCidr(string str, out NpgsqlCidr cidr) { cidr = default; @@ -32,19 +33,21 @@ public static bool TryParseCidr(string str, out (IPAddress, int) cidr) if (split.Length != 2) return false; - if (!IPAddress.TryParse(split[0], out cidr.Item1!)) + if (!IPAddress.TryParse(split[0], out var address)) return false; - if (!int.TryParse(split[1], out cidr.Item2)) + if (!byte.TryParse(split[1], out var mask)) return false; + cidr = new NpgsqlCidr(address, mask); + return true; } - public static string FormatCidr(this (IPAddress, int) cidr) + public static string FormatCidr(this NpgsqlCidr cidr) { var (addr, range) = cidr; return $"{addr}/{range}"; } -} \ No newline at end of file +} diff --git a/SS14.ServerHub.Shared/SS14.ServerHub.Shared.csproj b/SS14.ServerHub.Shared/SS14.ServerHub.Shared.csproj index b28561a..f0b1ed9 100644 --- a/SS14.ServerHub.Shared/SS14.ServerHub.Shared.csproj +++ b/SS14.ServerHub.Shared/SS14.ServerHub.Shared.csproj @@ -1,25 +1,25 @@ - net6.0 + net10.0 enable enable Library 11 - + - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/SS14.ServerHub/SS14.ServerHub.csproj b/SS14.ServerHub/SS14.ServerHub.csproj index e8696c7..abe0270 100644 --- a/SS14.ServerHub/SS14.ServerHub.csproj +++ b/SS14.ServerHub/SS14.ServerHub.csproj @@ -1,19 +1,19 @@ - net8.0 + net10.0 enable - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/SS14.Web.Tests/SS14.Web.Tests.csproj b/SS14.Web.Tests/SS14.Web.Tests.csproj index 3f97dfd..28007e7 100644 --- a/SS14.Web.Tests/SS14.Web.Tests.csproj +++ b/SS14.Web.Tests/SS14.Web.Tests.csproj @@ -1,20 +1,23 @@ - net8.0 + net10.0 false - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/SS14.Web.Tests/TestLegacySecretHandling.cs b/SS14.Web.Tests/TestLegacySecretHandling.cs new file mode 100644 index 0000000..8b67952 --- /dev/null +++ b/SS14.Web.Tests/TestLegacySecretHandling.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; +using SS14.Web.OpenId.Services; + +namespace SS14.Web.Tests; + +public class TestLegacySecretHandling +{ + // Test Hash generated using the old version of SS14.Web: + private const string DummyHash = "nodXBUVT5CFsKk+1JJivokaRZIOS1jDsN5QD2+iLkGc="; + + [TestCase( "2tXYv/rXpB91juHz13w2ib8zODPPmHQfXhCr5kuCLi8pKuTF", ExpectedResult = true)] + [TestCase( "2tXYv/rXpB91juHz13w2ib8zODPPmHQfXhCr5kuCLi8pKuTE", ExpectedResult = false)] + public bool TestValidation(string secret) + { + return SpaceApplicationManager.Functions.ValidateLegacySecret(secret, DummyHash); + } +} diff --git a/SS14.Web.Tests/TestMultipleClientSecretHandling.cs b/SS14.Web.Tests/TestMultipleClientSecretHandling.cs new file mode 100644 index 0000000..7901bc7 --- /dev/null +++ b/SS14.Web.Tests/TestMultipleClientSecretHandling.cs @@ -0,0 +1,44 @@ +#nullable enable +using System; +using JetBrains.Annotations; +using NUnit.Framework; +using SS14.Web.Models.Types; +using SS14.Web.OpenId.Services; + +namespace SS14.Web.Tests; + +public class TestMultipleClientSecretHandling +{ + [TestCase( "0.1755342104.key1111.desc", 0, ExpectedResult = 0)] + [TestCase( "0.1755342104.key1111.desc", 1, ExpectedResult = null)] + [TestCase( "0.1755342104.key1111.desc,1.1755342104.key2222.desc", 1, ExpectedResult = 1)] + public int? TestFindById(string secrets, int id) + { + return SpaceApplicationManager.Functions.FindById(secrets, id)?.Id; + } + + [TestCase( "0.1755342104.key1111.desc", "key2222", 2, 1)] + [TestCase( null, "key2222", 1, 0)] + [TestCase( "1.1755342104.key1111.desc", "key2222", 2, 2)] + public void TestAdd(string? secrets, string obfuscatedSecret, int count, int id) + { + var result = SpaceApplicationManager.Functions.AddSecret(secrets, obfuscatedSecret); + // 3 dots for each secret. + Assert.That(() => result.Item1.AsSpan().Count('.'), Is.EqualTo(count * 3)); + Assert.That(() => result.Item2.Id, Is.EqualTo(id)); + Assert.That(() => result.Item2.Description, Is.EqualTo(obfuscatedSecret[^6..])); + } + + [TestCase( "0.1755342104.key1111.desc", 0, ExpectedResult = null)] + [TestCase( "0.1755342104.key1111.desc", 1, ExpectedResult = "0.1755342104.key1111.desc")] + [TestCase( "0.1755342104.key1111.desc,1.1755342104.key2222.desc", 1, + ExpectedResult = "0.1755342104.key1111.desc")] + [TestCase( "0.1755342104.key1111.desc,1.1755342104.key2222.desc", 0, + ExpectedResult = "1.1755342104.key2222.desc")] + [TestCase( "0.1755342104.key1111.desc,1.1755342104.key2222.desc,2.1755342104.key3333.desc", 1, + ExpectedResult = "0.1755342104.key1111.desc,2.1755342104.key3333.desc")] + public string? TestRemove(string secrets, int id) + { + return SpaceApplicationManager.Functions.RemoveSecret(secrets, id); + } +} diff --git a/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml b/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml index 9a927ff..98ebcc9 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml +++ b/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml @@ -1,6 +1,5 @@ @page @model SS14.Web.Areas.Admin.Pages.Clients.Client - @{ ViewData["Title"] = $"OAuth Client: {Model.Title}"; } @@ -37,8 +36,8 @@
- - + +
@@ -56,42 +55,6 @@
-
-
-
- - -
-
-
- -
-
-
- - -
-
-
- -
-
-
- - -
-
-
- -
-
-
- - -
-
-
-
@@ -110,20 +73,6 @@
-
-
-
- - -
-
-
- -
- - -
-
@@ -134,43 +83,6 @@
-
-
-
- - -
-
-
- -
- - -
- -
-
-
- - -
-
-
- -
- - -
- -
-
-
- - -
-
-
-
@@ -185,135 +97,30 @@
-
- - -
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
-
-
- - -
-
-
- -
- - -
- -
- - -
- -
-
-
- - -
-
+
+

Lifetimes

+

0 = default

-
-
-
- - -
-
-
- -
-
-
- - -
-
-
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - + +
- - + +
- - + +
- +
Secrets
@@ -322,7 +129,7 @@ Description - Type + Is Legacy? Created @@ -332,16 +139,16 @@ { @secret.Description - @secret.Type - @secret.Created.ToUniversalTime() UTC + @secret.Legacy + @secret.CreatedOn.ToUniversalTime() UTC
- + } @@ -352,12 +159,9 @@ - - - +
Actions
- -Delete +Delete diff --git a/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml.cs b/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml.cs index 4f5ab11..70709c9 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml.cs +++ b/SS14.Web/Areas/Admin/Pages/Clients/Client.cshtml.cs @@ -1,26 +1,30 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; -using IdentityServer4.EntityFramework.Entities; -using IdentityServer4.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; +using OpenIddict.Abstractions; using SS14.Auth.Shared.Data; -using DbClient = IdentityServer4.EntityFramework.Entities.Client; +using SS14.Web.Models.Types; +using SS14.Web.OpenId; +using SS14.Web.OpenId.Extensions; +using SS14.Web.OpenId.Services; +using static OpenIddict.Abstractions.OpenIddictConstants.ConsentTypes; +using static OpenIddict.Abstractions.OpenIddictConstants.Settings.TokenLifetimes; namespace SS14.Web.Areas.Admin.Pages.Clients; public class Client : PageModel { - private readonly ApplicationDbContext _dbContext; + private readonly SpaceApplicationManager _appManager; - public Client(ApplicationDbContext dbContext) + public Client(SpaceApplicationManager appManager) { - _dbContext = dbContext; + _appManager = appManager; } [BindProperty] public InputModel Input { get; set; } @@ -28,298 +32,215 @@ public Client(ApplicationDbContext dbContext) [TempData] public string StatusMessage { get; set; } - public DbClient DbClient { get; set; } + public SpaceApplication? App { get; set; } - public string Title => DbClient.ClientName ?? DbClient.ClientId; - - public IEnumerable Secrets { get; set; } + public string? Title => App?.DisplayName ?? App?.ClientId; + public IEnumerable Secrets { get; set; } public sealed class CreateSecretModel { [Display(Name = "Desc")] - public string Description { get; set; } - public string Value { get; set; } - public string Type { get; set; } = "SharedSecret"; + public string? Description { get; set; } + + public string? Value { get; set; } } + public string[] ConsentTypes = [Implicit, Explicit, Systematic]; + // That's a lotta options. public sealed class InputModel { public bool Enabled { get; set; } - public string ClientId { get; set; } - public string ClientName { get; set; } - public string ProtocolType { get; set; } - public bool RequireClientSecret { get; set; } - public bool RequireConsent { get; set; } - public bool AllowRememberConsent { get; set; } - public bool AlwaysIncludeUserClaimsInIdToken { get; set; } + [Display(Name = "Client Id")] + public string? ClientId { get; set; } + [Display(Name = "Client Name")] + public string? ClientName { get; set; } + + // Explicit remembers the consent and Systematic forces a consent check every time + [Required] + [Display(Name = "Consent Type")] + [AllowedValues(Explicit, Implicit, Systematic)] + public string ConsentType { get; set; } = null!; + [Display(Name = "Require Pkce")] public bool RequirePkce { get; set; } public bool AllowPlainTextPkce { get; set; } - public bool RequireRequestObject { get; set; } - public string Description { get; set; } - public string ClientUri { get; set; } - public string LogoUri { get; set; } - public bool AllowAccessTokensViaBrowser { get; set; } - public string FrontChannelLogoutUri { get; set; } - public bool FrontChannelLogoutSessionRequired { get; set; } - public string BackChannelLogoutUri { get; set; } - public bool BackChannelLogoutSessionRequired { get; set; } + [Display(Name = "Home Page")] + public string? ClientUri { get; set; } + [Display(Name = "Logo Uri")] + public string? LogoUri { get; set; } + [Display(Name = "Allow Offline Access")] public bool AllowOfflineAccess { get; set; } + [Display(Name = "Identity Token Lifetime")] public int IdentityTokenLifetime { get; set; } - public string AllowedIdentityTokenSigningAlgorithms { get; set; } + [Display(Name = "Allowed ID Token Signing Alg.")] + public string? AllowedIdentityTokenSigningAlgorithms { get; set; } + [Display(Name = "Access Token Lifetime")] public int AccessTokenLifetime { get; set; } - public int AuthorizationCodeLifetime { get; set; } - public int? ConsentLifetime { get; set; } = null; - public int AbsoluteRefreshTokenLifetime { get; set; } - public int SlidingRefreshTokenLifetime { get; set; } - public int RefreshTokenUsage { get; set; } - public bool UpdateAccessTokenClaimsOnRefresh { get; set; } - public int RefreshTokenExpiration { get; set; } - public int AccessTokenType { get; set; } - public bool EnableLocalLogin { get; set; } - public bool IncludeJwtId { get; set; } - public bool AlwaysSendClientClaims { get; set; } - public string ClientClaimsPrefix { get; set; } - public string PairWiseSubjectSalt { get; set; } - public int? UserSsoLifetime { get; set; } - public string UserCodeType { get; set; } - public int DeviceCodeLifetime { get; set; } - + [Display(Name = "Refresh Token Lifetime")] + public int RefreshTokenLifetime { get; set; } [Display(Name = "Redirect URIs (one per line)")] - public string RedirectUris { get; set; } + public string? RedirectUris { get; set; } [Display(Name = "Allowed Scopes (one per line)")] - public string AllowedScopes { get; set; } + public string? AllowedScopes { get; set; } [Display(Name = "Allowed Grant Types (one per line)")] - public string AllowedGrantTypes { get; set; } + public string? AllowedGrantTypes { get; set; } - public string PostLogoutRedirectUris { get; set; } - public string IdentityProviderRestrictions { get; set; } - public string AllowedCorsOrigins { get; set; } + [Display(Name = "Post Logout Redirect Uris")] + public string? PostLogoutRedirectUris { get; set; } } - public async Task OnGetAsync(int id) + public async Task OnGetAsync(string id) { - DbClient = await _dbContext.Clients - .Include(c => c.RedirectUris) - .Include(c => c.AllowedScopes) - .Include(c => c.ClientSecrets) - .Include(c => c.IdentityProviderRestrictions) - .Include(c => c.PostLogoutRedirectUris) - .Include(c => c.AllowedCorsOrigins) - .Include(c => c.AllowedGrantTypes) - .SingleOrDefaultAsync(c => c.Id == id); - - if (DbClient == null) - { + App = await _appManager.FindByIdAsync(id); + if (App == null) return NotFound("Client not found"); - } + + var requirements = await _appManager.GetRequirementsAsync(App); + var settings = await _appManager.GetSettingsAsync(App); + var permissions = await _appManager.GetPermissionsAsync(App); + var redirectUris = await _appManager.GetRedirectUrisAsync(App); + var postLogoutRedirectUris = await _appManager.GetPostLogoutRedirectUrisAsync(App); + + var grantTypes = permissions + .Where(x => x.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) + .Select(x => x[OpenIddictConstants.Permissions.Prefixes.GrantType.Length..]); + + var scopes = permissions + .Where(x => x.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) + .Select(x => x[OpenIddictConstants.Permissions.Prefixes.Scope.Length..]); Input = new InputModel { - Enabled = DbClient.Enabled, - ClientId = DbClient.ClientId, - ClientName = DbClient.ClientName, - ProtocolType = DbClient.ProtocolType, - RequireClientSecret = DbClient.RequireClientSecret, - RequireConsent = DbClient.RequireConsent, - AllowRememberConsent = DbClient.AllowRememberConsent, - AlwaysIncludeUserClaimsInIdToken = DbClient.AlwaysIncludeUserClaimsInIdToken, - RequirePkce = DbClient.RequirePkce, - AllowPlainTextPkce = DbClient.AllowPlainTextPkce, - RequireRequestObject = DbClient.RequireRequestObject, - Description = DbClient.Description, - ClientUri = DbClient.ClientUri, - LogoUri = DbClient.LogoUri, - AllowAccessTokensViaBrowser = DbClient.AllowAccessTokensViaBrowser, - FrontChannelLogoutUri = DbClient.FrontChannelLogoutUri, - FrontChannelLogoutSessionRequired = DbClient.FrontChannelLogoutSessionRequired, - BackChannelLogoutUri = DbClient.BackChannelLogoutUri, - BackChannelLogoutSessionRequired = DbClient.BackChannelLogoutSessionRequired, - AllowOfflineAccess = DbClient.AllowOfflineAccess, - IdentityTokenLifetime = DbClient.IdentityTokenLifetime, - AllowedIdentityTokenSigningAlgorithms = DbClient.AllowedIdentityTokenSigningAlgorithms, - AccessTokenLifetime = DbClient.AccessTokenLifetime, - AuthorizationCodeLifetime = DbClient.AuthorizationCodeLifetime, - ConsentLifetime = DbClient.ConsentLifetime, - AbsoluteRefreshTokenLifetime = DbClient.AbsoluteRefreshTokenLifetime, - SlidingRefreshTokenLifetime = DbClient.SlidingRefreshTokenLifetime, - RefreshTokenUsage = DbClient.RefreshTokenUsage, - UpdateAccessTokenClaimsOnRefresh = DbClient.UpdateAccessTokenClaimsOnRefresh, - RefreshTokenExpiration = DbClient.RefreshTokenExpiration, - AccessTokenType = DbClient.AccessTokenType, - EnableLocalLogin = DbClient.EnableLocalLogin, - IncludeJwtId = DbClient.IncludeJwtId, - AlwaysSendClientClaims = DbClient.AlwaysSendClientClaims, - ClientClaimsPrefix = DbClient.ClientClaimsPrefix, - PairWiseSubjectSalt = DbClient.PairWiseSubjectSalt, - UserSsoLifetime = DbClient.UserSsoLifetime, - UserCodeType = DbClient.UserCodeType, - DeviceCodeLifetime = DbClient.DeviceCodeLifetime, - RedirectUris = string.Join("\n", DbClient.RedirectUris.Select(c => c.RedirectUri)), - AllowedGrantTypes = string.Join("\n", DbClient.AllowedGrantTypes.Select(c => c.GrantType)), - AllowedScopes = string.Join("\n", DbClient.AllowedScopes.Select(c => c.Scope)), - IdentityProviderRestrictions = - string.Join("\n", DbClient.IdentityProviderRestrictions.Select(c => c.Provider)), - PostLogoutRedirectUris = - string.Join("\n", DbClient.PostLogoutRedirectUris.Select(c => c.PostLogoutRedirectUri)), - AllowedCorsOrigins = string.Join("\n", DbClient.AllowedCorsOrigins.Select(c => c.Origin)), + Enabled = !await _appManager.IsDisabled(App), + ClientId = App.ClientId, + ClientName = App.DisplayName, + ConsentType = App.ConsentType ?? Explicit, + RequirePkce = requirements.Contains(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange), + AllowPlainTextPkce = await _appManager.GetAllowPlainPkceSetting(App), + ClientUri = App.WebsiteUrl, + LogoUri = App.LogoUri, + AllowOfflineAccess = permissions.Contains(OpenIddictConstants.Permissions.GrantTypes.RefreshToken), + AllowedIdentityTokenSigningAlgorithms = settings.GetValueOrDefault(OpenIdConstants.SigningAlgorithmSetting), + IdentityTokenLifetime = await _appManager.GetIdentityTokenLifetime(App), + AccessTokenLifetime = await _appManager.GetAccessTokenLifetime(App), + RefreshTokenLifetime = await _appManager.GetRefreshTokenLifetime(App), + RedirectUris = string.Join("\n", redirectUris), + AllowedGrantTypes = string.Join("\n", grantTypes), + AllowedScopes = string.Join("\n", scopes), + PostLogoutRedirectUris = string.Join("\n", postLogoutRedirectUris), }; CreateSecretInput = new CreateSecretModel(); - Secrets = DbClient.ClientSecrets; + Secrets = _appManager.ListSecrets(App); return Page(); } - public async Task OnPostSaveAsync(int id) + public async Task OnPostSaveAsync(string id) { - DbClient = await _dbContext.Clients - .Include(c => c.RedirectUris) - .Include(c => c.AllowedScopes) - .Include(c => c.IdentityProviderRestrictions) - .Include(c => c.PostLogoutRedirectUris) - .Include(c => c.AllowedCorsOrigins) - .Include(c => c.AllowedGrantTypes) - .SingleOrDefaultAsync(c => c.Id == id); - - if (DbClient == null) - { + var app = await _appManager.FindByIdAsync(id); + if (app == null) return NotFound("Client not found"); - } - DbClient.Enabled = Input.Enabled; - DbClient.ClientId = Input.ClientId; - DbClient.ClientName = Input.ClientName; - DbClient.ProtocolType = Input.ProtocolType; - DbClient.RequireClientSecret = Input.RequireClientSecret; - DbClient.RequireConsent = Input.RequireConsent; - DbClient.AllowRememberConsent = Input.AllowRememberConsent; - DbClient.AlwaysIncludeUserClaimsInIdToken = Input.AlwaysIncludeUserClaimsInIdToken; - DbClient.RequirePkce = Input.RequirePkce; - DbClient.AllowPlainTextPkce = Input.AllowPlainTextPkce; - DbClient.RequireRequestObject = Input.RequireRequestObject; - DbClient.Description = Input.Description; - DbClient.ClientUri = Input.ClientUri; - DbClient.LogoUri = Input.LogoUri; - DbClient.AllowAccessTokensViaBrowser = Input.AllowAccessTokensViaBrowser; - DbClient.FrontChannelLogoutUri = Input.FrontChannelLogoutUri; - DbClient.FrontChannelLogoutSessionRequired = Input.FrontChannelLogoutSessionRequired; - DbClient.BackChannelLogoutUri = Input.BackChannelLogoutUri; - DbClient.BackChannelLogoutSessionRequired = Input.BackChannelLogoutSessionRequired; - DbClient.AllowOfflineAccess = Input.AllowOfflineAccess; - DbClient.IdentityTokenLifetime = Input.IdentityTokenLifetime; - DbClient.AllowedIdentityTokenSigningAlgorithms = Input.AllowedIdentityTokenSigningAlgorithms; - DbClient.AccessTokenLifetime = Input.AccessTokenLifetime; - DbClient.AuthorizationCodeLifetime = Input.AuthorizationCodeLifetime; - DbClient.ConsentLifetime = Input.ConsentLifetime; - DbClient.AbsoluteRefreshTokenLifetime = Input.AbsoluteRefreshTokenLifetime; - DbClient.SlidingRefreshTokenLifetime = Input.SlidingRefreshTokenLifetime; - DbClient.RefreshTokenUsage = Input.RefreshTokenUsage; - DbClient.UpdateAccessTokenClaimsOnRefresh = Input.UpdateAccessTokenClaimsOnRefresh; - DbClient.RefreshTokenExpiration = Input.RefreshTokenExpiration; - DbClient.AccessTokenType = Input.AccessTokenType; - DbClient.EnableLocalLogin = Input.EnableLocalLogin; - DbClient.IncludeJwtId = Input.IncludeJwtId; - DbClient.AlwaysSendClientClaims = Input.AlwaysSendClientClaims; - DbClient.ClientClaimsPrefix = Input.ClientClaimsPrefix; - DbClient.PairWiseSubjectSalt = Input.PairWiseSubjectSalt; - DbClient.UserSsoLifetime = Input.UserSsoLifetime; - DbClient.UserCodeType = Input.UserCodeType; - DbClient.DeviceCodeLifetime = Input.DeviceCodeLifetime; - DbClient.RedirectUris = (Input.RedirectUris ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientRedirectUri {RedirectUri = c}) - .ToList(); - DbClient.AllowedScopes = (Input.AllowedScopes ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientScope {Scope = c}) - .ToList(); - DbClient.AllowedGrantTypes = (Input.AllowedGrantTypes ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientGrantType {GrantType = c}) - .ToList(); - DbClient.IdentityProviderRestrictions = (Input.IdentityProviderRestrictions ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientIdPRestriction {Provider = c}) - .ToList(); - DbClient.PostLogoutRedirectUris = (Input.PostLogoutRedirectUris ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientPostLogoutRedirectUri {PostLogoutRedirectUri = c}) - .ToList(); - DbClient.AllowedCorsOrigins = (Input.AllowedCorsOrigins ?? "") - .Replace("\r", "") - .Split("\n", StringSplitOptions.RemoveEmptyEntries) - .Select(c => new ClientCorsOrigin {Origin = c}) - .ToList(); - - await _dbContext.SaveChangesAsync(); - - StatusMessage = "Changes saved"; + var descriptor = new OpenIddictApplicationDescriptor(); + await _appManager.PopulateAsync(descriptor, app); - return RedirectToPage(new {id}); - } + descriptor.ClientId = Input.ClientId; + descriptor.DisplayName = Input.ClientName; + descriptor.ConsentType = Input.ConsentType; + descriptor.Settings[OpenIdConstants.DisabledSetting] = Input.Enabled ? "false" : "true"; + descriptor.Settings[OpenIdConstants.AllowPlainPkce] = Input.AllowPlainTextPkce ? "true" : "false"; + descriptor.Settings[OpenIdConstants.SigningAlgorithmSetting] = Input.AllowedIdentityTokenSigningAlgorithms ?? string.Empty; - public async Task OnPostCreateSecretAsync(int id) - { - DbClient = await _dbContext.Clients - .Include(c => c.RedirectUris) - .SingleOrDefaultAsync(c => c.Id == id); + SetTokenLifetime(descriptor, AccessToken, Input.AccessTokenLifetime); + SetTokenLifetime(descriptor, IdentityToken, Input.IdentityTokenLifetime); + SetTokenLifetime(descriptor, RefreshToken, Input.RefreshTokenLifetime); - if (DbClient == null) + descriptor.Permissions.RemoveWhere(x => x.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)); + + foreach (var grantType in Input.AllowedGrantTypes?.Split("\n", StringSplitOptions.RemoveEmptyEntries) ?? []) { - return NotFound("Client not found"); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.GrantType+grantType.Trim()); } - var value = CreateSecretInput.Value; - if (string.IsNullOrWhiteSpace(value)) + descriptor.Permissions.RemoveWhere(x => x.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)); + + foreach (var scope in Input.AllowedScopes?.Split("\n", StringSplitOptions.RemoveEmptyEntries) ?? []) { - var bytes = new byte[24]; - var rng = RandomNumberGenerator.Create(); - rng.GetBytes(bytes); - value = Convert.ToBase64String(bytes); + descriptor.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope+scope.Trim()); } - var secret = new ClientSecret + descriptor.RedirectUris.Clear(); + foreach (var redirectUri in Input.RedirectUris?.Split("\n", StringSplitOptions.RemoveEmptyEntries) ?? []) { - Description = CreateSecretInput.Description, - Type = CreateSecretInput.Type, - Value = value.Sha256() - }; + descriptor.RedirectUris.Add(new Uri(redirectUri.Trim())); + } - DbClient.ClientSecrets ??= new List(); - DbClient.ClientSecrets.Add(secret); + descriptor.PostLogoutRedirectUris.Clear(); + foreach (var redirectUri in Input.PostLogoutRedirectUris?.Split("\n", StringSplitOptions.RemoveEmptyEntries) ?? []) + { + descriptor.PostLogoutRedirectUris.Add(new Uri(redirectUri.Trim())); + } - await _dbContext.SaveChangesAsync(); + if (Input.RequirePkce) + descriptor.Requirements.Add(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange); + else + descriptor.Requirements.Remove(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange); - StatusMessage = $"Secret created. Value: {value}"; + if (Input.AllowOfflineAccess) + descriptor.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken); + else + descriptor.Permissions.Remove(OpenIddictConstants.Permissions.GrantTypes.RefreshToken); + app.WebsiteUrl = Input.ClientUri; + app.LogoUri = Input.LogoUri; + + await _appManager.UpdateAsync(app, descriptor); return RedirectToPage(new {id}); } - public async Task OnPostDeleteSecretAsync(int secret) + private void SetTokenLifetime(OpenIddictApplicationDescriptor descriptor, string setting, int lifetime) { - var dbSecret = await _dbContext.ClientSecrets - .SingleOrDefaultAsync(c => c.Id == secret); - - if (dbSecret == null) + if (lifetime <= 0) { - return NotFound("Secret not found"); + descriptor.Settings.Remove(setting); + return; } - _dbContext.ClientSecrets.Remove(dbSecret); + descriptor.Settings[setting] = lifetime.ToString(); + } - await _dbContext.SaveChangesAsync(); + public async Task OnPostCreateSecretAsync(string id) + { + var app = await _appManager.FindByIdAsync(id); + if (app == null) + return NotFound("Client not found"); - StatusMessage = "Secret deleted."; + var secret = CreateSecretInput.Value; + if (string.IsNullOrWhiteSpace(secret)) + secret = Convert.ToBase64String(RandomNumberGenerator.GetBytes(36)); - return RedirectToPage(new {id = dbSecret.ClientId}); + // Ensure an empty description is null. + var description = CreateSecretInput.Description; + if (string.IsNullOrWhiteSpace(description)) + description = null; + + await _appManager.AddSecret(app, secret, description); + StatusMessage = $"Secret created. Value: {secret}"; + return RedirectToPage(new {id}); + } + + public async Task OnPostDeleteSecretAsync(string id, int secret) + { + var app = await _appManager.FindByIdAsync(id); + if (app == null) + return NotFound("Client not found"); + + await _appManager.RemoveSecret(app, secret); + StatusMessage = "Secret deleted."; + return RedirectToPage(new {id}); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml b/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml index b03013c..e4be3b8 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml +++ b/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml @@ -9,11 +9,11 @@
- -
\ No newline at end of file + + diff --git a/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml.cs b/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml.cs index 7f2921f..b19bd49 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml.cs +++ b/SS14.Web/Areas/Admin/Pages/Clients/ConfirmDelete.cshtml.cs @@ -1,50 +1,43 @@ -using System.Threading.Tasks; +#nullable enable + +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; using SS14.Auth.Shared.Data; -using DbClient = IdentityServer4.EntityFramework.Entities.Client; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Admin.Pages.Clients; public class ConfirmDelete : PageModel { - private readonly ApplicationDbContext _dbContext; + private readonly SpaceApplicationManager _applicationManager; - public ConfirmDelete(ApplicationDbContext dbContext) + public ConfirmDelete(SpaceApplicationManager applicationManager) { - _dbContext = dbContext; + _applicationManager = applicationManager; } - public DbClient DbClient { get; set; } - public string Title => DbClient.ClientName ?? DbClient.ClientId; - - public async Task OnGetAsync(int id) - { - DbClient = await _dbContext.Clients.FirstOrDefaultAsync(c => c.Id == id); + public SpaceApplication? App { get; set; } + public string? Title => App?.DisplayName ?? App?.ClientId; - if (DbClient == null) - { + public async Task OnGetAsync(string id) + { + App = await _applicationManager.FindByIdAsync(id); + if (App == null) return NotFound("Unknown client"); - } return Page(); } - public async Task OnPostDeleteAsync(int id) + public async Task OnPostDeleteAsync(string id) { - DbClient = await _dbContext.Clients.FirstOrDefaultAsync(c => c.Id == id); - - if (DbClient == null) - { + var app = await _applicationManager.FindByIdAsync(id); + if (app == null) return NotFound("Unknown client"); - } - - _dbContext.Clients.Remove(DbClient); - await _dbContext.SaveChangesAsync(); + await _applicationManager.DeleteAsync(app); TempData["StatusMessage"] = "OAuth client deleted"; return RedirectToPage("./Index"); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml b/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml index 83e71c1..4467b1f 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml +++ b/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml @@ -13,7 +13,7 @@ @if (TempData.ContainsKey("StatusMessage")) { - + }
@@ -30,19 +30,19 @@ - @foreach (var (client, userClient) in Model.Clients) + @foreach (var app in Model.Apps) { - @client.ClientId - @client.ClientName + @app.ClientId + @app.DisplayName - @if (userClient?.SpaceUser is { } user) + @if (app.SpaceUserId is { } id) { - @user.UserName + @(await Model.GetUserNameAsync(id)) } - View + View } diff --git a/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml.cs b/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml.cs index affa465..4ac1b71 100644 --- a/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml.cs +++ b/SS14.Web/Areas/Admin/Pages/Clients/Index.cshtml.cs @@ -1,50 +1,73 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; +using OpenIddict.Abstractions; using SS14.Auth.Shared.Data; -using DbClient = IdentityServer4.EntityFramework.Entities.Client; +using SS14.Web.OpenId; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Admin.Pages.Clients; public class Index : PageModel { - private readonly ApplicationDbContext _dbContext; + private readonly SpaceApplicationManager _appManager; + private readonly SpaceUserManager _userManager; - public IEnumerable<(DbClient, UserOAuthClient)> Clients { get; set; } + public IEnumerable Apps { get; set; } - public Index(ApplicationDbContext dbContext) + public Index(SpaceApplicationManager appManager, SpaceUserManager userManager) { - _dbContext = dbContext; + _appManager = appManager; + _userManager = userManager; } public async Task OnGetAsync() { - // This is a left join - var query = from c in _dbContext.Clients - join uc in _dbContext.UserOAuthClients.Include(c => c.SpaceUser) - on c.Id equals uc.ClientId into grouping - from uc in grouping.DefaultIfEmpty() - orderby c.Created - select new { c, uc }; - - Clients = (await query.ToListAsync()).Select(c => (c.c, c.uc)); + Apps = await _appManager.ListAsync().ToListAsync(); } public async Task OnPostNewClientAsync() { - var client = new IdentityServer4.EntityFramework.Entities.Client + + var appDescriptor = new OpenIddictApplicationDescriptor { ClientId = Guid.NewGuid().ToString(), + ClientSecret = Convert.ToBase64String(RandomNumberGenerator.GetBytes(36)), + ClientType = OpenIddictConstants.ClientTypes.Confidential, + ConsentType = OpenIddictConstants.ConsentTypes.Explicit, + DisplayName = "New Client", + Requirements = { OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange }, + Settings = {{OpenIdConstants.AllowPlainPkce, "true"}}, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Introspection, + OpenIddictConstants.Permissions.Endpoints.EndSession, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.ResponseTypes.Code, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Roles, + }, }; - // ReSharper disable once MethodHasAsyncOverload - _dbContext.Clients.Add(client); - await _dbContext.SaveChangesAsync(); + var app = await _appManager.CreateAsync(appDescriptor); + + TempData["ShowSecret"] = 0; + TempData["ShowSecretValue"] = appDescriptor.ClientSecret; + return RedirectToPage("./Client", new { id = app.Id }); + } - return RedirectToPage("./Client", new { id = client.Id }); + + public async Task GetUserNameAsync(Guid userId) + { + return (await _userManager.FindByIdAsync(userId.ToString()))?.UserName; } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml b/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml index 9f94a18..f472afe 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml +++ b/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml @@ -1,10 +1,13 @@ @page +@using Microsoft.Extensions.Primitives +@using OpenIddict.Abstractions +@using SS14.Web.Extensions +@using SS14.Web.Helpers @model SS14.Web.Areas.Identity.Pages.Account.Consent @{ Layout = null; } - @@ -19,7 +22,12 @@ - + @* Pass request parameters through so they can be handled by the accept/deny handlers*@ + @foreach (var parameter in HttpContext.Request.HasFormContentType ? (IEnumerable>) HttpContext.Request.Form : HttpContext.Request.Query) + { + + } +
Space Station 14 @@ -27,58 +35,56 @@
-

Authorize @Model.AuthRequest.Client.ClientName

- @if (!string.IsNullOrWhiteSpace(@Model.AuthRequest.Client.LogoUri)) +

Authorize @*@Model.AuthRequest.Client.ClientName*@

+ @if (!string.IsNullOrWhiteSpace(Model.Application?.LogoUri)) { - @Model.AuthRequest.Client.ClientName + @Model.Application?.DisplayName } -
+
- \ No newline at end of file + diff --git a/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml.cs index da4559e..3485e03 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Consent.cshtml.cs @@ -1,83 +1,141 @@ -using System.Linq; +#nullable enable +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; -using IdentityServer4.Models; -using IdentityServer4.Services; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; +using OpenIddict.Abstractions; +using OpenIddict.Server.AspNetCore; using SS14.Auth.Shared.Data; +using SS14.Web.Helpers; +using SS14.Web.Models.Types; +using SS14.Web.OpenId.Services; +using SS14.Web.OpenId.Types; +using static OpenIddict.Abstractions.OpenIddictConstants; namespace SS14.Web.Areas.Identity.Pages.Account; - public sealed class Consent : PageModel { - private readonly IIdentityServerInteractionService _interaction; - private readonly ApplicationDbContext _dbContext; - private readonly ILogger _logger; + private readonly OpenIdActionService _actionService; + + public OpenIddictRequest? AuthRequest { get; private set; } + public SpaceApplication? Application { get; private set; } + public ImmutableArray RequestScopes { get; private set; } + public string? ReturnUrl { get; set; } - public AuthorizationRequest AuthRequest { get; private set; } - public UserOAuthClient UserClient { get; private set; } - public string ReturnUrl { get; set; } - - [BindProperty] public InputModel Input { get; set; } + [TempData] + public bool IgnoreChallenge { get; set; } + + [BindProperty] public InputModel? Input { get; set; } [ValidateAntiForgeryToken] public sealed class InputModel { - public string ReturnUrl { get; set; } - public string Button { get; set; } + public string? Button { get; set; } } - - public Consent(IIdentityServerInteractionService interaction, ApplicationDbContext dbContext, ILogger logger) + + public Consent(OpenIdActionService actionService) { - _interaction = interaction; - _dbContext = dbContext; - _logger = logger; + _actionService = actionService; } - - public async Task OnGetAsync(string returnUrl) - { - ReturnUrl = returnUrl; - AuthRequest = await _interaction.GetAuthorizationContextAsync(returnUrl); - // Can be null if managed by hub admins. - // Though I doubt we'll ever have consent pages for those. - UserClient = await _dbContext.UserOAuthClients - .Include(oauth => oauth.Client) - .Include(oauth => oauth.SpaceUser) - .SingleOrDefaultAsync(oauth => oauth.Client.ClientId == AuthRequest.Client.ClientId); + public async Task OnGetAsync(string? returnUrl) + { + return await HandleAuthorization(returnUrl); + } - return Page(); + public async Task OnPostAsync(string? returnUrl) + { + return Input?.Button switch + { + "no" => Forbid(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme), + "yes" => await HandleAccept(), + _ => await HandleAuthorization(returnUrl) + }; } - public async Task OnPostAsync() + public static string GetScopeName(string scope) => scope switch { - var request = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl); - if (request == null) - return RedirectToAction("Error", "Home"); + Scopes.Email => "Email", + Scopes.Roles => "Roles", + _ => scope, + }; - var response = new ConsentResponse(); - if (Input.Button == "yes") - { - var resources = request.ValidatedResources.Resources; + private async Task HandleAuthorization(string? returnUrl) + { + ReturnUrl = returnUrl; + AuthRequest = HttpContext.GetOpenIddictServerRequest(); - // TODO: Maybe allow configuring this? - response.RememberConsent = true; - response.ScopesValuesConsented = resources.ApiScopes.Select(x => x.Name) - .Concat(resources.IdentityResources.Select(x => x.Name)); - } - else if (Input.Button == "no") + if (AuthRequest is null) + return BadRequest(); + + var result = await HttpContext.AuthenticateAsync(); + var validation = _actionService.ValidateOpenIdAuthentication( + HttpContext, + IgnoreChallenge, + result, + AuthRequest); + + // Prevent infinite redirection loops + IgnoreChallenge = true; + + switch (validation.IsSuccess) { - response.Error = AuthorizationError.AccessDenied; + case false when validation.Error.IsChallenge: + return Challenge(validation.Error.Properties!); + case false: + return AuthResults.Forbid(validation.Error.Error!, GetErrorDescription(validation.Error.Error!)); } - else + + // Ensure the request contains the openid and profile scopes + RequestScopes = [..AuthRequest.GetScopes().Union(["openid", "profile"])]; + var authorization = await _actionService.AuthorizeActionAsync(AuthRequest, RequestScopes); + Application = authorization.Application; + + return authorization.Type switch { - return BadRequest(); - } + AuthorizationResult.ResultType.Forbidden => + AuthResults.Forbid(authorization.ErrorName ?? "", GetErrorDescription(authorization.ErrorName)), - await _interaction.GrantConsentAsync(request, response); + AuthorizationResult.ResultType.Error => + BadRequest(GetErrorDescription(authorization.ErrorName)), - return Redirect(Input.ReturnUrl); + AuthorizationResult.ResultType.SignIn => + SignIn(authorization.Principal!, authenticationScheme: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme), + + _ => Page(), + }; } -} \ No newline at end of file + + private async Task HandleAccept() + { + var request = HttpContext.GetOpenIddictServerRequest(); + if (request is null) + return BadRequest("The OpenID Connect request cannot be retrieved."); + + var result = await _actionService.AcceptActionAsync(request, request.GetScopes()); + + return result.Type switch + { + ConsentResult.ResultType.SignIn => + SignIn(result.Principal!, + authenticationScheme: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme), + ConsentResult.ResultType.Forbid => + AuthResults.Forbid(result.ErrorName ?? "", GetErrorDescription(result.ErrorName)), + _ => BadRequest(GetErrorDescription(result.ErrorName)) + }; + } + + // ReSharper disable once ArrangeMethodOrOperatorBody + private static string GetErrorDescription(string? error) => error switch + { + OpenIdActionService.ApplicationNotFoundError => "No application for the given client id.", + Errors.AccessDenied => "The logged in user is not allowed to access this client application.", + Errors.ConsentRequired => "Interactive user consent is required.", + Errors.UnsupportedGrantType => "The specified grant type is not supported.", + Errors.InvalidGrant => "The token is no longer valid.", + _ => "An unknown error occurred.", + }; +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml b/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml index 2d4b3d1..eff5c32 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml @@ -18,8 +18,8 @@

- @foreach (var app in Model.OAuthClients) + @foreach (var app in Model.Apps) { - @app.Client.ClientName + @app.DisplayName }
diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml.cs index eb79a93..7af3bd8 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/Developer.cshtml.cs @@ -1,33 +1,30 @@ -using System.Collections.Generic; -using System.Linq; +#nullable enable +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; using SS14.Auth.Shared.Data; +using SS14.Web.OpenId.Extensions; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; public class Developer : PageModel { - private readonly ApplicationDbContext _dbContext; private readonly UserManager _userManager; + private readonly SpaceApplicationManager _appManager; - public List OAuthClients { get; set; } + public List Apps { get; set; } = []; - public Developer(ApplicationDbContext dbContext, UserManager userManager) + public Developer(UserManager userManager, SpaceApplicationManager appManager) { - _dbContext = dbContext; _userManager = userManager; + _appManager = appManager; } public async Task OnGetAsync() { var user = await _userManager.GetUserAsync(User); - - OAuthClients = await _dbContext.UserOAuthClients - .Include(oa => oa.Client) - .Where(oa => oa.SpaceUser == user) - .ToListAsync(); + Apps = await _appManager.FindApplicationsByUserId(user!.Id); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml index d074d0b..0d2b93f 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml @@ -3,7 +3,7 @@ @{ Layout = "/Views/Shared/_Layout.cshtml"; - ViewData["Title"] = $"Delete OAuth app {Model.App.Client.ClientName}?"; + ViewData["Title"] = $"Delete OAuth app {Model.App.DisplayName}?"; }
diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml.cs index 26761b1..ba3ae7b 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/ConfirmDelete.cshtml.cs @@ -1,56 +1,52 @@ -using System.Threading.Tasks; +#nullable enable +using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using SS14.Auth.Shared.Data; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Identity.Pages.Account.Manage.OAuthApps; public class ConfirmDelete : PageModel { - private readonly ApplicationDbContext _dbContext; private readonly UserManager _userManager; + private readonly SpaceApplicationManager _appManager; - public ConfirmDelete(ApplicationDbContext dbContext, UserManager userManager) + public SpaceApplication App { get; set; } = null!; + + public ConfirmDelete(UserManager userManager, SpaceApplicationManager appManager) { - _dbContext = dbContext; _userManager = userManager; + _appManager = appManager; } - public UserOAuthClient App { get; set; } - - public async Task OnGetAsync(int client) + public async Task OnGetAsync(string client) { var user = await _userManager.GetUserAsync(User); - App = await _dbContext.UserOAuthClients.Include(a => a.Client) - .SingleOrDefaultAsync(ac => ac.UserOAuthClientId == client); - - if (App == null) + var app = await _appManager.FindByIdAsync(client); + if (app == null) return NotFound(); - if (!Manage.VerifyAppAccess(user, App)) + if (app.SpaceUserId != user!.Id) return Forbid(); + App = app; return Page(); } - public async Task OnPostDeleteAsync(int client) + public async Task OnPostDeleteAsync(string client) { var user = await _userManager.GetUserAsync(User); - App = await _dbContext.UserOAuthClients.Include(a => a.Client) - .SingleOrDefaultAsync(ac => ac.UserOAuthClientId == client); - - if (App == null) + var app = await _appManager.FindByIdAsync(client); + if (app == null) return NotFound(); - if (!Manage.VerifyAppAccess(user, App)) + if (app.SpaceUserId != user!.Id) return Forbid(); - _dbContext.Remove(App.Client); - - await _dbContext.SaveChangesAsync(); - + await _appManager.DeleteAsync(app); return RedirectToPage("../Developer"); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Create.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Create.cshtml.cs index ada6ff1..542046e 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Create.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Create.cshtml.cs @@ -1,43 +1,51 @@ -using System; -using System.Collections.Generic; +#nullable enable +using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Security.Cryptography; using System.Threading.Tasks; -using IdentityServer4.EntityFramework.Entities; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using OpenIddict.Abstractions; using SS14.Auth.Shared.Data; +using SS14.Web.Models; +using SS14.Web.OpenId; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Identity.Pages.Account.Manage.OAuthApps; [ValidateAntiForgeryToken] public class Create : PageModel { - private readonly ApplicationDbContext _dbContext; private readonly UserManager _userManager; + private readonly SpaceApplicationManager _appManager; - [BindProperty] public InputModel Input { get; set; } + [BindProperty] public InputModel Input { get; set; } = null!; public sealed class InputModel { [Required] [DisplayName("Application name")] - public string Name { get; set; } + public string Name { get; set; } = null!; + //[Url(ErrorMessage = "The Homepage URL field is not a valid fully-qualified http or https URL.")] [Required] + [SpaceUrl] [DisplayName("Homepage URL")] - public string HomepageUrl { get; set; } + public string HomepageUrl { get; set; } = null!; + //[Url(ErrorMessage = "The Authorization callback URL field is not a valid fully-qualified http or https URL.")] [Required] + [SpaceUrl] [DisplayName("Authorization callback URL")] - public string CallbackUrl { get; set; } + public string CallbackUrl { get; set; } = null!; } - public Create(ApplicationDbContext dbContext, UserManager userManager) + public Create(UserManager userManager, SpaceApplicationManager appManager) { - _dbContext = dbContext; _userManager = userManager; + _appManager = appManager; } public void OnGet() @@ -46,42 +54,42 @@ public void OnGet() public async Task OnPostAsync() { - var user = await _userManager.GetUserAsync(User); + if (!ModelState.IsValid) + return Page(); - var client = new Client + var user = await _userManager.GetUserAsync(User); + var appDescriptor = new OpenIddictApplicationDescriptor { - ClientName = Input.Name, ClientId = Guid.NewGuid().ToString(), - AllowedGrantTypes = new List - { - new() { GrantType = "authorization_code" } - }, - AllowedScopes = new List - { - new() { Scope = "openid" }, - new() { Scope = "profile" }, - new() { Scope = "email" } - }, - AllowedIdentityTokenSigningAlgorithms = "PS256", - RedirectUris = new List + ClientSecret = Convert.ToBase64String(RandomNumberGenerator.GetBytes(36)), + ClientType = OpenIddictConstants.ClientTypes.Confidential, + ConsentType = OpenIddictConstants.ConsentTypes.Explicit, + DisplayName = Input.Name, + RedirectUris = { new Uri(Input.CallbackUrl) }, + Requirements = { OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange }, + Settings = {{OpenIdConstants.AllowPlainPkce, "true"}}, + Permissions = { - new() { RedirectUri = Input.CallbackUrl } + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Introspection, + OpenIddictConstants.Permissions.Endpoints.EndSession, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.ResponseTypes.Code, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Roles, }, - ClientUri = Input.HomepageUrl, - RequireConsent = true }; - var userClient = new UserOAuthClient - { - SpaceUser = user, - Client = client - }; - - _dbContext.Clients.Add(client); - _dbContext.UserOAuthClients.Add(userClient); - - await _dbContext.SaveChangesAsync(); + var app = await _appManager.CreateAsync(appDescriptor); + app.SpaceUserId = user!.Id; + app.WebsiteUrl = Input.HomepageUrl; + await _appManager.UpdateAsync(app); - return RedirectToPage("Manage", new { client = userClient.UserOAuthClientId }); + TempData["ShowSecret"] = 0; + TempData["ShowSecretValue"] = appDescriptor.ClientSecret; + return RedirectToPage("Manage", new { client = app.Id }); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml index 11cdf3b..790585f 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml @@ -4,7 +4,7 @@ @{ Layout = "/Views/Shared/_Layout.cshtml"; - ViewData["Title"] = $"Manage OAuth app {Model.App.Client.ClientName}"; + ViewData["Title"] = $"Manage OAuth app {Model.App.DisplayName}"; }
@@ -16,10 +16,10 @@
- Client ID: @Model.App.Client.ClientId
+ Client ID: @Model.App.ClientId
Only Authorization Code flow is accepted
Available scopes are: openid, profile, email
- SS14 implements OpenID Connect via IdentityServer4. See .well-known/openid-configuration for config and endpoints. + SS14 implements OpenID Connect via OpenIddict. See .well-known/openid-configuration for config and endpoints.
@@ -67,26 +67,24 @@
- @foreach (var secret in Model.App.Client.ClientSecrets.OrderByDescending(a => a.Created)) + @foreach (var secret in Model.Secrets.OrderByDescending(a => a.CreatedOn)) { var show = Model.ShowSecret == secret.Id; - if (secret.Type != "SharedSecret") - continue;
-
- @(show ? Model.ShowSecretValue : secret.Description) -
- Created at @secret.Created.ToUniversalTime() UTC -
-
- -
+
+ @(show ? Model.ShowSecretValue : $"*****{secret.Description}") +
+ Created at @secret.CreatedOn UTC
+
+ +
+
}
@@ -95,10 +93,10 @@
- Delete + Delete @if (User.IsInRole(AuthConstants.RoleSysAdmin)) { - Hub Admin + Hub Admin }
@@ -107,4 +105,4 @@ @section Scripts { -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml.cs index a7c252f..9ff55e2 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/OAuthApps/Manage.cshtml.cs @@ -2,29 +2,33 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; -using IdentityServer4.EntityFramework.Entities; -using IdentityServer4.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.EntityFrameworkCore; +using OpenIddict.Abstractions; using SS14.Auth.Shared.Data; +using SS14.Web.Models; +using SS14.Web.Models.Types; +using SS14.Web.OpenId; +using SS14.Web.OpenId.Extensions; +using SS14.Web.OpenId.Services; namespace SS14.Web.Areas.Identity.Pages.Account.Manage.OAuthApps; public class Manage : PageModel { - private readonly ApplicationDbContext _dbContext; private readonly UserManager _userManager; + private readonly SpaceApplicationManager _appManager; - public UserOAuthClient App { get; set; } + public SpaceApplication App { get; set; } [BindProperty] public InputModel Input { get; set; } - [TempData] public int ShowSecret { get; set; } + public List Secrets { get; set; } + + [TempData] public int? ShowSecret { get; set; } [TempData] public string ShowSecretValue { get; set; } public sealed class InputModel @@ -34,117 +38,116 @@ public sealed class InputModel public string Name { get; set; } [Required] + [SpaceUrl] [DisplayName("Homepage URL")] public string HomepageUrl { get; set; } [Required] + [SpaceUrl] [DisplayName("Authorization callback URL")] public string CallbackUrl { get; set; } [Required] [DisplayName("Require PKCE")] public bool RequirePkce { get; set; } - + [Required] [DisplayName("Allow PS256 signing")] public bool AllowPS256 { get; set; } = true; } - public Manage(ApplicationDbContext dbContext, UserManager userManager) + public Manage(UserManager userManager, SpaceApplicationManager appManager) { - _dbContext = dbContext; _userManager = userManager; + _appManager = appManager; } - public async Task OnGetAsync(int client) + public async Task OnGetAsync(string client) { if (await GetAppAndVerifyAccess(client) is { } err) return err; Input = new InputModel { - Name = App.Client.ClientName, - CallbackUrl = App.Client.RedirectUris.FirstOrDefault()?.RedirectUri ?? "", - HomepageUrl = App.Client.ClientUri, - RequirePkce = App.Client.RequirePkce, - AllowPS256 = App.Client.AllowedIdentityTokenSigningAlgorithms?.Contains("PS256") ?? false + Name = App.DisplayName, + CallbackUrl = await _appManager.GetFirstRedirectUrl(App) ?? "", + HomepageUrl = App.WebsiteUrl ?? "", + RequirePkce = await _appManager.GetRequiresPkce(App), + AllowPS256 = await _appManager.GetPs256Setting(App), }; + Secrets = _appManager.ListSecrets(App); + return Page(); } - public async Task OnPostUpdateAsync(int client) + public async Task OnPostUpdateAsync(string client) { + if (!ModelState.IsValid) + return Page(); + if (await GetAppAndVerifyAccess(client) is { } err) return err; - App.Client.ClientName = Input.Name; - App.Client.RedirectUris = new List { new() { RedirectUri = Input.CallbackUrl } }; - App.Client.ClientUri = Input.HomepageUrl; - App.Client.RequirePkce = Input.RequirePkce; - App.Client.AllowedIdentityTokenSigningAlgorithms = Input.AllowPS256 ? "PS256" : null; + var descriptor = new OpenIddictApplicationDescriptor(); + await _appManager.PopulateAsync(descriptor, App); + descriptor.DisplayName = Input.Name; + descriptor.RedirectUris.Clear(); + descriptor.RedirectUris.Add(new Uri(Input.CallbackUrl)); + + if (Input.RequirePkce) + { + descriptor.Requirements.Add(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange); + } + else + { + descriptor.Requirements.Remove(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange); + } + + descriptor.Settings[OpenIdConstants.SigningAlgorithmSetting] = Input.AllowPS256 + ? OpenIddictConstants.Algorithms.RsaSsaPssSha256 + : null; - await _dbContext.SaveChangesAsync(); + App.WebsiteUrl = Input.HomepageUrl; + await _appManager.UpdateAsync(App, descriptor); return RedirectToPage(new { client }); } - public async Task OnPostCreateSecretAsync(int client) + public async Task OnPostCreateSecretAsync(string client) { if (await GetAppAndVerifyAccess(client) is { } err) return err; var secretVal = Convert.ToBase64String(RandomNumberGenerator.GetBytes(36)); - - var secret = new ClientSecret - { - Created = DateTime.UtcNow, - Type = "SharedSecret", - Description = $"*****{secretVal[^6..]}", - Value = secretVal.Sha256() - }; - - App.Client.ClientSecrets ??= new List(); - App.Client.ClientSecrets.Add(secret); - - await _dbContext.SaveChangesAsync(); - ShowSecret = secret.Id; + var secretInfo = await _appManager.AddSecret(App, secretVal); + ShowSecret = secretInfo.Id; ShowSecretValue = secretVal; - + return RedirectToPage(new { client }); } - - public async Task OnPostDeleteSecretAsync(int client, int secret) + + public async Task OnPostDeleteSecretAsync(string client, int secret) { if (await GetAppAndVerifyAccess(client) is { } err) return err; - var dbSecret = App.Client.ClientSecrets.Find(p => p.Id == secret); - if (dbSecret == null) - return NotFound("Secret not found"); - - _dbContext.ClientSecrets.Remove(dbSecret); + await _appManager.RemoveSecret(App, secret); - await _dbContext.SaveChangesAsync(); - return RedirectToPage(new {id = client}); } // Null return indicates everything good. - private async Task GetAppAndVerifyAccess(int client) + private async Task GetAppAndVerifyAccess(string client) { var user = await _userManager.GetUserAsync(User); - App = await _dbContext.UserOAuthClients - .Include(c => c.Client) - .ThenInclude(c => c.RedirectUris) - .Include(c => c.Client) - .ThenInclude(c => c.ClientSecrets) - .SingleOrDefaultAsync(oa => oa.UserOAuthClientId == client); + App = await _appManager.FindByIdAsync(client); if (App == null) return NotFound(); + // ReSharper disable once ConvertIfStatementToReturnStatement if (!VerifyAppAccess(user, App)) return Forbid(); @@ -153,8 +156,8 @@ private async Task GetAppAndVerifyAccess(int client) public static bool VerifyAppAccess( SpaceUser user, - UserOAuthClient userClient) + SpaceApplication app) { - return user == userClient.SpaceUser; + return user == app.SpaceUser; } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Register.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Register.cshtml.cs index bfe5e2f..bea965d 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -150,4 +150,4 @@ public async Task GenerateEmailConfirmLink( protocol: Request.Scheme); return callbackUrl; } -} \ No newline at end of file +} diff --git a/SS14.Web/Extensions/PatreonExtension.cs b/SS14.Web/Extensions/PatreonExtension.cs new file mode 100644 index 0000000..0fa0c4c --- /dev/null +++ b/SS14.Web/Extensions/PatreonExtension.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SS14.Auth.Shared.Config; + +namespace SS14.Web.Extensions; + +public static class PatreonExtension +{ + public static void AddPatreon(this WebApplicationBuilder builder) + { + builder.Services.AddScoped(); + var patreonSection = builder.Configuration.GetSection("Patreon"); + var patreonCfg = patreonSection.Get(); + + if (patreonCfg?.ClientId == null || patreonCfg.ClientSecret == null) + return; + + builder.Services.AddAuthentication() + // Rider is dumb that null is valid. + // It disables Patreon as an external login. + .AddPatreon("Patreon", null!, options => + { + // Patreon docs lied you don't need this to see memberships to your own campaign. + // options.Scope.Add("identity.memberships"); + options.Includes.Add("memberships.currently_entitled_tiers"); + options.ClientId = patreonCfg.ClientId; + options.ClientSecret = patreonCfg.ClientSecret; + + options.Events.OnCreatingTicket += context => + { + var handler = context.HttpContext.RequestServices.GetService(); + return handler!.HookCreatingTicket(context); + }; + + options.Events.OnTicketReceived += context => + { + var handler = context.HttpContext.RequestServices.GetService(); + return handler!.HookReceivedTicket(context); + }; + }); + } +} diff --git a/SS14.Web/Helpers/AuthResults.cs b/SS14.Web/Helpers/AuthResults.cs new file mode 100644 index 0000000..ac2ef04 --- /dev/null +++ b/SS14.Web/Helpers/AuthResults.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using OpenIddict.Server.AspNetCore; +using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants; + +namespace SS14.Web.Helpers; + +public static class AuthResults +{ + public static ForbidResult Forbid(string error, string description) + { + return new ForbidResult( + authenticationSchemes: [OpenIddictServerAspNetCoreDefaults.AuthenticationScheme], + properties: new AuthenticationProperties(new Dictionary + { + [Properties.Error] = error, + [Properties.ErrorDescription] = description, + })); + } +} diff --git a/SS14.Web/Helpers/DestinationHelper.cs b/SS14.Web/Helpers/DestinationHelper.cs new file mode 100644 index 0000000..b5cd9e2 --- /dev/null +++ b/SS14.Web/Helpers/DestinationHelper.cs @@ -0,0 +1,45 @@ +#nullable enable +using System.Collections.Generic; +using System.Security.Claims; +using OpenIddict.Abstractions; + +namespace SS14.Web.Helpers; + +public static class DestinationHelper +{ + /// + /// Yield the access token destination and additionally the identity token destination + /// if the given scope is present in the claim + /// + /// The claim to return destinations for + /// The scope to use for checking if identity destination should be returned + /// Destinations.AccessToken and optionally the Destinations.IdentityToken + public static IEnumerable Destination(Claim claim, string scope) + { + yield return OpenIddictConstants.Destinations.AccessToken; + + if (claim.Subject!.HasScope(scope)) + yield return OpenIddictConstants.Destinations.IdentityToken; + } + + /// + /// Yields the given destination + /// + /// The destination to yield + /// The given destination + /// This is useful in a switch for determining the destinations of a claim + public static IEnumerable Destination(string destination) + { + yield return destination; + } + + /// + /// Completely skips a claim by yielding nothing + /// + /// Nothing + /// This is useful in a switch for determining the destinations of a claim + public static IEnumerable Destination() + { + yield break; + } +} diff --git a/SS14.Web/Helpers/ScopeHelper.cs b/SS14.Web/Helpers/ScopeHelper.cs new file mode 100644 index 0000000..6f1810d --- /dev/null +++ b/SS14.Web/Helpers/ScopeHelper.cs @@ -0,0 +1,15 @@ +using static OpenIddict.Abstractions.OpenIddictConstants; + +namespace SS14.Web.Helpers; + +public static class ScopeHelper +{ + // ReSharper disable once ArrangeMethodOrOperatorBody + public static string GetScopeName(string scope) => scope switch + { + Scopes.Profile => "Name and user ID", + Scopes.Email => "Email", + Scopes.Roles => "Roles", + _ => scope, + }; +} diff --git a/SS14.Web/Models/SpaceUrlAttribute.cs b/SS14.Web/Models/SpaceUrlAttribute.cs new file mode 100644 index 0000000..4df65d8 --- /dev/null +++ b/SS14.Web/Models/SpaceUrlAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace SS14.Web.Models; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)] +public class SpaceUrlAttribute : ValidationAttribute +{ + protected override ValidationResult IsValid(object value, ValidationContext context) + { + var url = value?.ToString(); + // ReSharper disable once ConvertIfStatementToReturnStatement + if (!Uri.TryCreate(url, UriKind.Absolute, out _)) + return new ValidationResult(ErrorMessage ?? "Invalid URL"); + + return ValidationResult.Success; + } +} diff --git a/SS14.Web/Models/Types/ClientSecretInfo.cs b/SS14.Web/Models/Types/ClientSecretInfo.cs new file mode 100644 index 0000000..a2d0ba8 --- /dev/null +++ b/SS14.Web/Models/Types/ClientSecretInfo.cs @@ -0,0 +1,5 @@ +using System; + +namespace SS14.Web.Models.Types; + +public sealed record ClientSecretInfo(int Id, DateTimeOffset CreatedOn, string Description, bool Legacy); diff --git a/SS14.Web/Models/Types/Result.cs b/SS14.Web/Models/Types/Result.cs new file mode 100644 index 0000000..fe746b7 --- /dev/null +++ b/SS14.Web/Models/Types/Result.cs @@ -0,0 +1,38 @@ +#nullable enable +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + +namespace SS14.Web.Models.Types; + +public record Result(TResult? Value, TError? Error) where TResult : class where TError : class +{ + [PublicAPI] + [MemberNotNullWhen(false, nameof(Error))] + [MemberNotNullWhen(true, nameof(Value))] + public bool IsSuccess => Error == null; + + public bool TryGetResult([NotNullWhen(true)] out TResult? result) + { + result = Value; + return IsSuccess; + } + + public static Result Success(TResult value) + { + return new Result(value, null); + } + + public static Result Failure(TError error) + { + return new Result(null, error); + } +} + +/// +/// Used to signify that a result does not return any data, but was successful. +/// +public sealed record Void +{ + [PublicAPI] + public static Void Nothing = new Void(); +} diff --git a/SS14.Web/OpenId/Configuration/CertificateOptions.cs b/SS14.Web/OpenId/Configuration/CertificateOptions.cs new file mode 100644 index 0000000..90ed474 --- /dev/null +++ b/SS14.Web/OpenId/Configuration/CertificateOptions.cs @@ -0,0 +1,9 @@ +#nullable enable +namespace SS14.Web.OpenId.Configuration; + +public sealed class CertificateOptions +{ + public string Path { get; set; } = null!; + public string? Password { get; set; } + public string? Algorithm { get; set; } +} diff --git a/SS14.Web/OpenId/Configuration/OpenIdCertificateConfiguration.cs b/SS14.Web/OpenId/Configuration/OpenIdCertificateConfiguration.cs new file mode 100644 index 0000000..4351e73 --- /dev/null +++ b/SS14.Web/OpenId/Configuration/OpenIdCertificateConfiguration.cs @@ -0,0 +1,30 @@ +#nullable enable +using System.Collections.Generic; + +namespace SS14.Web.OpenId.Configuration; + +public sealed class OpenIdCertificateConfiguration +{ + public const string Name = "Certificates"; + + /// + /// Sets the default encryption algorithm if it didn't get overriden by the application. + /// If this is null the first available certificate will be used. + /// + /// + /// A certificate with the matching algorithm needs to be present + /// + public string? DefaultEncryptionAlgorithm { get; set; } + + /// + /// Sets the default encryption algorithm if it didn't get overriden by the application. + /// If this is null the first available certificate will be used. + /// + /// + /// A certificate with the matching algorithm needs to be present + /// + public string? DefaultSigningAlgorithm { get; set; } + + public List EncryptionCertificates { get; set; } = []; + public List SigningCertificates { get; set; } = []; +} diff --git a/SS14.Web/OpenId/EventHandlers/AuthorizationPkceVerificationHandler.cs b/SS14.Web/OpenId/EventHandlers/AuthorizationPkceVerificationHandler.cs new file mode 100644 index 0000000..3f2f601 --- /dev/null +++ b/SS14.Web/OpenId/EventHandlers/AuthorizationPkceVerificationHandler.cs @@ -0,0 +1,38 @@ +#nullable enable +using System.Threading.Tasks; +using OpenIddict.Abstractions; +using OpenIddict.Server; +using SS14.Web.OpenId.Extensions; +using SS14.Web.OpenId.Services; + +namespace SS14.Web.OpenId.EventHandlers; + +public sealed class AuthorizationPkceVerificationHandler : IOpenIddictServerHandler +{ + private readonly SpaceApplicationManager _applicationManager; + + public AuthorizationPkceVerificationHandler(SpaceApplicationManager applicationManager) + { + _applicationManager = applicationManager; + } + + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(int.MaxValue - 500) + .Build(); + + public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateAuthorizationRequestContext context) + { + var app = await _applicationManager.FindByClientIdAsync(context.Request.ClientId!); + if (app is null) + return; + + var method = context.Request.CodeChallengeMethod; + if (await _applicationManager.GetAllowPlainPkceSetting(app)) + return; + + if (method is OpenIddictConstants.CodeChallengeMethods.Plain) + context.Reject(error: OpenIddictConstants.Errors.InvalidRequest, description: "Plain PKCE not allowed."); + } +} diff --git a/SS14.Web/OpenId/EventHandlers/TokenSigningHandler.cs b/SS14.Web/OpenId/EventHandlers/TokenSigningHandler.cs new file mode 100644 index 0000000..9262b9d --- /dev/null +++ b/SS14.Web/OpenId/EventHandlers/TokenSigningHandler.cs @@ -0,0 +1,81 @@ +#nullable enable +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using OpenIddict.Abstractions; +using OpenIddict.Server; +using SS14.Web.OpenId.Configuration; +using static OpenIddict.Server.OpenIddictServerEvents; + +namespace SS14.Web.OpenId.EventHandlers; + +public class TokenSigningHandler : IOpenIddictServerHandler +{ + private readonly IOpenIddictApplicationManager _applicationManager; + + private readonly OpenIdCertificateConfiguration _config = new(); + + public TokenSigningHandler(IOpenIddictApplicationManager applicationManager, IConfiguration configuration) + { + _applicationManager = applicationManager; + + configuration.GetSection("OpenId").GetSection(OpenIdCertificateConfiguration.Name).Bind(_config); + } + + public static OpenIddictServerHandlerDescriptor Descriptor { get; } = + OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseScopedHandler() + .SetOrder(OpenIddictServerHandlers.Protection.AttachSecurityCredentials.Descriptor.Order + 500) + .Build(); + + public async ValueTask HandleAsync(GenerateTokenContext context) + { + if (context.Request?.ClientId is null) + return; + + // Ignore client assertion + if (context.TokenType is OpenIddictConstants.TokenTypeIdentifiers.Private.ClientAssertion) + return; + + var app = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (app is null) + return; + + var settings = await _applicationManager.GetSettingsAsync(app); + + var encryptionAlgorithm = settings.TryGetValue(OpenIdConstants.EncryptionAlgorithmSetting, out var encryptionAlg) + ? encryptionAlg + : _config.DefaultEncryptionAlgorithm; + + encryptionAlgorithm = string.IsNullOrEmpty(encryptionAlgorithm) ? _config.DefaultEncryptionAlgorithm : encryptionAlgorithm; ; + + if (encryptionAlgorithm is not null) + { + foreach (var credential in context.Options.EncryptionCredentials) + { + if (!credential.Enc.Equals(encryptionAlgorithm)) + continue; + + context.EncryptionCredentials = credential; + break; + } + } + + var signingAlgorithm = settings.TryGetValue(OpenIdConstants.SigningAlgorithmSetting, out var signingAlg) + ? signingAlg + : _config.DefaultSigningAlgorithm; + + signingAlgorithm = string.IsNullOrEmpty(signingAlgorithm) ? _config.DefaultSigningAlgorithm : signingAlgorithm; + + if (signingAlgorithm is not null) + { + foreach (var credential in context.Options.SigningCredentials) + { + if (!credential.Algorithm.Equals(signingAlgorithm)) + continue; + + context.SigningCredentials = credential; + break; + } + } + } +} diff --git a/SS14.Web/OpenId/Extensions/ApplicationManagerExtensions.cs b/SS14.Web/OpenId/Extensions/ApplicationManagerExtensions.cs new file mode 100644 index 0000000..d219145 --- /dev/null +++ b/SS14.Web/OpenId/Extensions/ApplicationManagerExtensions.cs @@ -0,0 +1,96 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using SS14.Auth.Shared.Data; + +namespace SS14.Web.OpenId.Extensions; + +public static class ApplicationManagerExtensions +{ + public static async ValueTask GetFirstRedirectUrl( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + var callbackUris = await manager.GetRedirectUrisAsync(app); + return callbackUris.IsEmpty ? null : callbackUris[0]; + } + + public static async ValueTask GetRequiresPkce( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + var requirements = await manager.GetRequirementsAsync(app); + return requirements.Contains(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange); + } + + public static async ValueTask GetPs256Setting( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await manager.CheckSetting(app, OpenIdConstants.SigningAlgorithmSetting, OpenIddictConstants.Algorithms.RsaSsaPssSha256); + } + + public static async ValueTask GetAllowPlainPkceSetting( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await manager.CheckSetting(app, OpenIdConstants.AllowPlainPkce, "true"); + } + + public static async ValueTask IsDisabled( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await manager.CheckSetting(app, OpenIdConstants.DisabledSetting, "true"); + } + + public static async ValueTask CheckSetting(this OpenIddictApplicationManager manager, + SpaceApplication app, + string setting, + string comparand) + { + var settings = await manager.GetSettingsAsync(app); + return settings.TryGetValue(setting, out var value) + && value.Equals(comparand); + } + + public static async ValueTask> FindApplicationsByUserId(this OpenIddictApplicationManager manager, Guid userId, CancellationToken cancel = default) + { + return await manager.ListAsync(x => x.Where(a => a.SpaceUserId == userId) + , cancel).ToListAsync(cancellationToken: cancel); + } + + public static async ValueTask GetAccessTokenLifetime( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await GetLifetime(manager, app, OpenIddictConstants.Settings.TokenLifetimes.AccessToken); + } + + public static async ValueTask GetIdentityTokenLifetime( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await GetLifetime(manager, app, OpenIddictConstants.Settings.TokenLifetimes.IdentityToken); + } + + public static async ValueTask GetRefreshTokenLifetime( + this OpenIddictApplicationManager manager, + SpaceApplication app) + { + return await GetLifetime(manager, app, OpenIddictConstants.Settings.TokenLifetimes.RefreshToken); + } + + private static async ValueTask GetLifetime(OpenIddictApplicationManager manager, + SpaceApplication app, + string settingName) + { + var settings = await manager.GetSettingsAsync(app); + return int.Parse(settings.GetValueOrDefault(settingName, "0")); + } +} diff --git a/SS14.Web/OpenId/Extensions/OpenIdExtension.cs b/SS14.Web/OpenId/Extensions/OpenIdExtension.cs new file mode 100644 index 0000000..3423bdc --- /dev/null +++ b/SS14.Web/OpenId/Extensions/OpenIdExtension.cs @@ -0,0 +1,99 @@ +#nullable enable +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.IdentityModel.Tokens; +using OpenIddict.Abstractions; +using SS14.Auth.Shared.Data; +using SS14.Web.OpenId.Configuration; +using SS14.Web.OpenId.EventHandlers; +using SS14.Web.OpenId.Services; +using static SS14.Auth.Shared.Data.OpeniddictDefaultTypes; + +namespace SS14.Web.OpenId.Extensions; + +public static class OpenIdExtension +{ + public static void AddOpenIdConnect(this WebApplicationBuilder builder) + { + var openId = builder.Services.AddOpenIddict(); + openId.AddCore(options => + { + options.UseEntityFrameworkCore().UseDbContext() + .ReplaceDefaultEntities(); + + options.ReplaceApplicationManager(); + options.UseQuartz(); + }); + + openId.AddValidation().UseLocalServer(); + openId.AddValidation().UseAspNetCore(); + + openId.AddServer().Configure(config => builder.Configuration.Bind("OpenId:Server", config)); + openId.AddServer().UseAspNetCore().EnableAuthorizationEndpointPassthrough().EnableStatusCodePagesIntegration(); + ConfigureCertificates(openId, builder); + + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + if (builder.Configuration.GetValue("SeedTestData", false)) + builder.Services.AddHostedService(); + } + + private static void ConfigureCertificates(OpenIddictBuilder openId, WebApplicationBuilder builder) + { + builder.Services.Add(AuthorizationPkceVerificationHandler.Descriptor.ServiceDescriptor); + openId.AddServer().AddEventHandler(AuthorizationPkceVerificationHandler.Descriptor); + + builder.Services.Add(TokenSigningHandler.Descriptor.ServiceDescriptor); + openId.AddServer().AddEventHandler(TokenSigningHandler.Descriptor); + + if (builder.Environment.IsDevelopment()) + { + openId.AddServer().AddDevelopmentEncryptionCertificate().AddDevelopmentSigningCertificate(); + return; + } + + var config = builder.Configuration + .GetSection("OpenId") + .GetSection(OpenIdCertificateConfiguration.Name).Get(); + + if (config is null) + throw new ArgumentException("OpenId:Server:CertificateConfiguration is not set."); + + foreach (var encryptionCertificate in config.EncryptionCertificates) + { + using var encryptionCert = File.OpenRead(encryptionCertificate.Path); + openId.AddServer().AddEncryptionCertificate(encryptionCert, encryptionCertificate.Password); + } + + foreach (var signingCertificate in config.SigningCertificates) + { + using var signingCert = File.OpenRead(signingCertificate.Path); + + if (signingCertificate.Algorithm == null) + { + openId.AddServer().AddSigningCertificate(signingCert, signingCertificate.Password); + } + else + { + using var buffer = new MemoryStream(); + signingCert.CopyTo(buffer); + var cert = X509CertificateLoader.LoadPkcs12( + buffer.ToArray(), + signingCertificate.Password, + X509KeyStorageFlags.EphemeralKeySet); + + var key = new X509SecurityKey(cert); + var credentials = new SigningCredentials(key, signingCertificate.Algorithm); + openId.AddServer().AddSigningCredentials(credentials); + } + } + } +} diff --git a/SS14.Web/OpenId/OpenIdConstants.cs b/SS14.Web/OpenId/OpenIdConstants.cs new file mode 100644 index 0000000..d0f96bc --- /dev/null +++ b/SS14.Web/OpenId/OpenIdConstants.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using OpenIddict.Abstractions; + +namespace SS14.Web.OpenId; + +public static class OpenIdConstants +{ + public const string EncryptionAlgorithmSetting = "space:EncryptionAlgorithm"; + public const string SigningAlgorithmSetting = "space:SigningAlgorithm"; + public const string DisabledSetting = "space:Disabled"; + public const string AllowPlainPkce = "space:AllowPlainPkce"; +} diff --git a/SS14.Web/OpenId/Services/IdentityClaimsProvider.cs b/SS14.Web/OpenId/Services/IdentityClaimsProvider.cs new file mode 100644 index 0000000..fb64e66 --- /dev/null +++ b/SS14.Web/OpenId/Services/IdentityClaimsProvider.cs @@ -0,0 +1,67 @@ +#nullable enable +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using OpenIddict.Abstractions; +using SS14.Auth.Shared.Data; +using SS14.Web.Helpers; +using Claims = OpenIddict.Abstractions.OpenIddictConstants.Claims; + +namespace SS14.Web.OpenId.Services; + +public class IdentityClaimsProvider +{ + private readonly UserManager _userManager; + + public IdentityClaimsProvider(UserManager userManager) + { + _userManager = userManager; + } + + /// + /// Provides claims for the specified identity. + /// + /// + /// + /// The identity for which claims are to be provided. + /// A task representing the asynchronous operation. + public async Task ProvideClaimsAsync(string userId, ImmutableArray scopes, ClaimsIdentity identity) + { + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + return; + + var username = await _userManager.GetUserNameAsync(user) ?? ""; + + identity.AddClaim(new Claim(Claims.Subject, await _userManager.GetUserIdAsync(user))); + + if (scopes.Contains(OpenIddictConstants.Scopes.Profile)) + { + identity.AddClaim(new Claim(Claims.Name, username)); + identity.AddClaim(new Claim(Claims.PreferredUsername, await _userManager.GetUserNameAsync(user) ?? username)); + } + + if (scopes.Contains(OpenIddictConstants.Scopes.Email)) + identity.AddClaim(new Claim(Claims.Email, await _userManager.GetEmailAsync(user) ?? "")); + + if (scopes.Contains(OpenIddictConstants.Scopes.Roles)) + identity.AddClaims(Claims.Role, ImmutableArray.Create([.. await _userManager.GetRolesAsync(user)])); + } + + /// + /// Returns whether the claim gets supplied in the ID token, the Access token or just via the introspection endpoint + /// + /// The claim to return the destinations for + /// A list of destinations for the claim + public IEnumerable GetDestinations(Claim claim) => claim.Type switch + { + Claims.Name or Claims.PreferredUsername => DestinationHelper.Destination(claim, OpenIddictConstants.Scopes.Profile), + Claims.Email => DestinationHelper.Destination(claim, OpenIddictConstants.Scopes.Email), + Claims.Role => DestinationHelper.Destination(claim, OpenIddictConstants.Scopes.Roles), + Claims.Address => DestinationHelper.Destination(claim, OpenIddictConstants.Scopes.Address), + "AspNet.Identity.SecurityStamp" => DestinationHelper.Destination(), + _ => DestinationHelper.Destination(OpenIddictConstants.Destinations.AccessToken) + }; +} diff --git a/SS14.Web/OpenId/Services/OpenIdActionService.cs b/SS14.Web/OpenId/Services/OpenIdActionService.cs new file mode 100644 index 0000000..e02b7b0 --- /dev/null +++ b/SS14.Web/OpenId/Services/OpenIdActionService.cs @@ -0,0 +1,220 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using SS14.Auth.Shared.Data; +using SS14.Web.Models.Types; +using SS14.Web.OpenId.Types; +using static OpenIddict.Abstractions.OpenIddictConstants; +using Void = SS14.Web.Models.Types.Void; + +namespace SS14.Web.OpenId.Services; + +/** + * Handles all the OpenIddict action plumbing. Used inside the consent page. + */ +public class OpenIdActionService +{ + public const string ApplicationNotFoundError = "application_not_found"; + + private readonly SignedInIdentityService _signedInIdentity; + private readonly IdentityClaimsProvider _claimsProvider; + private readonly IOpenIddictScopeManager _scopeManager; + private readonly IOpenIddictAuthorizationManager _authorizationManager; + private readonly OpenIddictApplicationManager _applicationManager; + private readonly ILogger _logger; + + public OpenIdActionService(SignedInIdentityService signedInIdentity, + IdentityClaimsProvider claimsProvider, + OpenIddictApplicationManager applicationManager, + IOpenIddictAuthorizationManager authorizationManager, + ILogger logger, + IOpenIddictScopeManager scopeManager) + { + _signedInIdentity = signedInIdentity; + _claimsProvider = claimsProvider; + _applicationManager = applicationManager; + _authorizationManager = authorizationManager; + _logger = logger; + _scopeManager = scopeManager; + } + + /// + /// Validates the current authentication state for an OpenID Connect request and determines whether + /// the request can continue, must fail silently, or requires an interactive authentication challenge. + /// + /// The current HTTP context. + /// Whether to ignore the challenge. Set when the user was redirected already to prevent redirection loops. + /// The authentication result for the current request. + /// The OpenIddict request (e.g., prompt/max_age). + /// + /// A successful result when authentication is enough. Otherwise, a failure describing either + /// a login requirement (e.g., prompt=none) or a challenge with redirect properties. + /// + public Result ValidateOpenIdAuthentication( + HttpContext context, + bool ignoreChallenge, + AuthenticateResult auth, + OpenIddictRequest request) + { + // Auth succeeded and nothing is forcing re-authentication + if (auth.Succeeded + && !request.HasPromptValue(PromptValues.Login) + && request.MaxAge is not 0 + && (request.MaxAge is null || auth.Properties?.IssuedUtc is null || TimeProvider.System.GetUtcNow() - auth.Properties.IssuedUtc < TimeSpan.FromSeconds(request.MaxAge.Value))) + { + return Result.Success(Void.Nothing); + } + + if (ignoreChallenge || request.HasPromptValue(PromptValues.None)) + return Result.Failure(AuthenticationValidationFailure.LoginRequired); + + var properties = new AuthenticationProperties + { + RedirectUri = context.Request.PathBase + context.Request.Path + QueryString.Create( + context.Request.HasFormContentType ? context.Request.Form : context.Request.Query), + }; + + return Result.Failure(AuthenticationValidationFailure.Challenge(properties)); + } + + /// + /// Processes an authorization request and decides whether to sign in immediately, require user consent, + /// or deny the request based on the application's consent type, prompt values, and existing authorizations. + /// + /// The OpenIddict authorization request. + /// The requested scopes. + /// An representing SignIn/Consent/Forbidden/Error. + public async Task AuthorizeActionAsync(OpenIddictRequest request, ImmutableArray scopes) + { + var application = await _applicationManager.FindByClientIdAsync(request.ClientId!); + if (application is null) + return AuthorizationResult.Error(null, ApplicationNotFoundError); + + if (!await _signedInIdentity.IsAvailableAsync()) + { + _logger.LogWarning("Signed in user is not available."); + return AuthorizationResult.Forbidden(application, Errors.AccessDenied); + } + + var authorizations = new List(); + + // Ensure the user will be prompted if a prompt was requested later in the switch statement below + if (!request.HasPromptValue(PromptValues.Consent)) + { + authorizations = await _authorizationManager.FindAsync( + subject: await _signedInIdentity.GetUserIdAsync(), + client: await _applicationManager.GetIdAsync(application), + status: Statuses.Valid, + type: AuthorizationTypes.Permanent, + scopes: scopes + ).ToListAsync(); + } + + return await _applicationManager.GetConsentTypeAsync(application) switch + { + ConsentTypes.External when authorizations.Count is 0 => + AuthorizationResult.Forbidden(application, Errors.AccessDenied), + + ConsentTypes.Implicit or ConsentTypes.External or ConsentTypes.Explicit when authorizations.Count is not 0 => + await HandleSignIn(request, application, authorizations), + + ConsentTypes.Explicit or ConsentTypes.Systematic when request.HasPromptValue(PromptValues.None) => + AuthorizationResult.Forbidden(application, Errors.ConsentRequired), + + _ => AuthorizationResult.Consent(application, scopes), + }; + } + + /// + /// Accepts the consent decision and returns a sign-in result containing an authorized principal for token issuance. + /// + /// The OpenIddict authorization request. + /// The approved scopes. + /// A representing SignIn/Forbid/Error. + public async Task AcceptActionAsync(OpenIddictRequest request, ImmutableArray scopes) + { + var application = await _applicationManager.FindByClientIdAsync(request.ClientId!); + if (application is null) + return ConsentResult.Error(ApplicationNotFoundError); + + if (!await _signedInIdentity.IsAvailableAsync()) + { + _logger.LogWarning("Signed in user is not available."); + return ConsentResult.Forbid(Errors.AccessDenied); + } + + var userId = await _signedInIdentity.GetUserIdAsync(); + var authorizations = await _authorizationManager.FindAsync( + subject: userId, + client: await _applicationManager.GetIdAsync(application), + status: Statuses.Valid, + type: AuthorizationTypes.Permanent, + scopes: scopes + ).ToListAsync(); + + if (authorizations.Count is 0 && await _applicationManager.HasConsentTypeAsync(application, ConsentTypes.External)) + return ConsentResult.Forbid(Errors.AccessDenied); + + var principal = await CreateAuthorizedPrincipal( + userId!, + application, + authorizations, + scopes, + _claimsProvider.GetDestinations); + + return ConsentResult.SignIn(principal); + } + + private async Task HandleSignIn( + OpenIddictRequest request, + SpaceApplication application, + List authorizations) + { + var scopes = request.GetScopes(); + var userId = await _signedInIdentity.GetUserIdAsync(); + var identity = await CreateAuthorizedPrincipal(userId!, application, authorizations, scopes, _claimsProvider.GetDestinations); + + return AuthorizationResult.SignIn(identity); + } + + private async Task CreateAuthorizedPrincipal( + string userId, + SpaceApplication application, + List authorizations, + ImmutableArray scopes, + Func> destinationsSelector) + { + var identity = new ClaimsIdentity( + authenticationType: TokenValidationParameters.DefaultAuthenticationType, + nameType: Claims.Name, + roleType: Claims.Role); + + await _claimsProvider.ProvideClaimsAsync(userId, scopes, identity); + + identity.SetScopes(scopes); + identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync()); + + var authorization = authorizations.LastOrDefault(); + authorization ??= await _authorizationManager.CreateAsync( + identity: identity, + subject: userId, + client: (await _applicationManager.GetIdAsync(application))!, + type: AuthorizationTypes.Permanent, + scopes: identity.GetScopes() + ); + + identity.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); + identity.SetDestinations(destinationsSelector); + return new ClaimsPrincipal(identity); + } +} diff --git a/SS14.Web/OpenId/Services/SignedInIdentityService.cs b/SS14.Web/OpenId/Services/SignedInIdentityService.cs new file mode 100644 index 0000000..59e0760 --- /dev/null +++ b/SS14.Web/OpenId/Services/SignedInIdentityService.cs @@ -0,0 +1,43 @@ +#nullable enable +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using SS14.Auth.Shared.Data; + +namespace SS14.Web.OpenId.Services; + +public class SignedInIdentityService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public SignedInIdentityService(IHttpContextAccessor httpContextAccessor, UserManager userManager, SignInManager signInManager) + { + _httpContextAccessor = httpContextAccessor; + _userManager = userManager; + _signInManager = signInManager; + } + + public async Task IsAvailableAsync() + { + if (_httpContextAccessor.HttpContext is not { } context) + return false; + + var result = await context.AuthenticateAsync(); + return result.Succeeded; + } + + public async Task GetUserIdAsync() + { + if (_httpContextAccessor.HttpContext is not { } context) + return null; + + var user = await _userManager.GetUserAsync(context.User); + if (user is null) + return null; + + return await _userManager.GetUserIdAsync(user); + } +} diff --git a/SS14.Web/OpenId/Services/SpaceApplicationManager.cs b/SS14.Web/OpenId/Services/SpaceApplicationManager.cs new file mode 100644 index 0000000..8d3d6dc --- /dev/null +++ b/SS14.Web/OpenId/Services/SpaceApplicationManager.cs @@ -0,0 +1,228 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using OpenIddict.Core; +using SS14.Auth.Shared.Data; +using SS14.Web.Models.Types; + +namespace SS14.Web.OpenId.Services; + +public class SpaceApplicationManager( + IOpenIddictApplicationCache cache, + ILogger> logger, + IOptionsMonitor options, + IOpenIddictApplicationStore store) + : OpenIddictApplicationManager(cache, logger, options, store) +{ + private const string LegacySecretPrefix = "_OLD_"; + + public ClientSecretInfo? FindSecretById(SpaceApplication app, int id, CancellationToken ct = default) + { + return app.ClientSecret is null ? null : Functions.FindById(app.ClientSecret, id); + } + + public List ListSecrets(SpaceApplication app, CancellationToken ct = default) + { + var result = new List(); + if (app.ClientSecret is null) + return result; + + var span = app.ClientSecret.AsSpan(); + var secrets = span.SplitAny(',', '.'); + while (secrets.MoveNext()) + { + result.Add(Functions.ParseSecretInfo(span, ref secrets)); + } + + return result; + } + + public async ValueTask AddSecret(SpaceApplication app, string secret, string? desc = null, CancellationToken ct = default) + { + var key = await base.ObfuscateClientSecretAsync(secret, ct); + var (secretsString, info) = Functions.AddSecret(app.ClientSecret, key, desc ?? secret[^6..]); + await Store.SetClientSecretAsync(app, secretsString, ct); + await base.UpdateAsync(app, ct); + return info; + } + + public async ValueTask RemoveSecret(SpaceApplication app, int id, CancellationToken ct = default) + { + if (app.ClientSecret is null) + return; + + var result = Functions.RemoveSecret(app.ClientSecret, id); + if (result == app.ClientSecret) + return; + + await Store.SetClientSecretAsync(app, result, ct); + await base.UpdateAsync(app, ct); + } + + protected override async ValueTask ObfuscateClientSecretAsync(string secret, CancellationToken ct = new()) + { + var legacy = secret.StartsWith(LegacySecretPrefix); + if (legacy) + secret = secret[5..]; + + var secrets = secret.Split(','); + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); + var obfuscatedSecrets = new string[secrets.Length]; + + for (var i = 0; i < secrets.Length; i++) + { + // Legacy secrets are from a migration and are already hashed + var obfuscatedSecret = !legacy + ? await base.ObfuscateClientSecretAsync(secrets[i], ct) + : $"{LegacySecretPrefix}{secrets[i]}"; + var description = legacy ? "legacy-please-regenerate" : secrets[i][^6..]; + obfuscatedSecrets[i] = $"{i}.{timestamp}.{obfuscatedSecret}.{description}"; + } + + return string.Join(",", obfuscatedSecrets); + } + + protected override async ValueTask ValidateClientSecretAsync(string secret, + string comparand, + CancellationToken ct = new()) + { + var secrets = comparand.Split(','); + foreach (var pair in secrets) + { + var value = pair.Split('.')[2]; + var legacy = value.StartsWith(LegacySecretPrefix); + + // ReSharper disable once ConvertIfStatementToSwitchStatement + if (legacy) + value = value[LegacySecretPrefix.Length..]; + + if (!legacy && await base.ValidateClientSecretAsync(secret, value, ct)) + return true; + + if (legacy && Functions.ValidateLegacySecret(secret, value)) + return true; + } + + return false; + } + + public static class Functions + { + // MoveNext() needs to have been called for parts once already. This is to allow checking the client secret id + public static ClientSecretInfo ParseSecretInfo(ReadOnlySpan span, ref MemoryExtensions.SpanSplitEnumerator parts) + { + var id = span[parts.Current]; + parts.MoveNext(); + var timestamp = span[parts.Current]; + parts.MoveNext(); + var isLegacy = span[parts.Current]; + parts.MoveNext(); + var description = span[parts.Current]; + + return new ClientSecretInfo( + int.Parse(id), + DateTimeOffset.FromUnixTimeSeconds(long.Parse(timestamp)), + description.ToString(), + isLegacy.StartsWith(LegacySecretPrefix) + ); + } + + public static ClientSecretInfo? FindById(string secret, int id) + { + var span = secret.AsSpan(); + var parts = span.SplitAny(',', '.'); + while (parts.MoveNext()) + { + if (int.Parse(span[parts.Current]) == id) + return Functions.ParseSecretInfo(span, ref parts); + + parts.MoveNext(); //timestamp + parts.MoveNext(); //key + parts.MoveNext(); //description + } + + return null; + } + + public static (string, ClientSecretInfo) AddSecret(string? secrets, string obfuscatedSecret, string? desc = null) + { + var id = 0; + + if (secrets is not null) + { + var span = secrets.AsSpan(); + var parts = span.SplitAny(',', '.'); + while (parts.MoveNext()) + { + if (int.Parse(span[parts.Current]) >= id) + id = int.Parse(span[parts.Current]) + 1; + + parts.MoveNext(); //timestamp + parts.MoveNext(); //key + parts.MoveNext(); //description + } + } + + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); + var description = desc ?? obfuscatedSecret[^6..]; + var secretInfoString = $"{id}.{timestamp}.{obfuscatedSecret}.{description}"; + var secretsString = secrets is null ? secretInfoString : $"{secrets},{secretInfoString}"; + var secretInfo = new ClientSecretInfo(id, DateTimeOffset.UtcNow, description, false); + return (secretsString, secretInfo); + } + + public static string? RemoveSecret(string secrets, int id) + { + var span = secrets.AsSpan(); + var parts = span.SplitAny(',', '.'); + while (parts.MoveNext()) + { + if (int.Parse(span[parts.Current]) == id) + { + var start = span[..parts.Current.Start.Value]; + if (start.StartsWith(',')) + start = start[1..]; + + if (start.EndsWith(',')) + start = start[..^1]; + + parts.MoveNext(); //timestamp + parts.MoveNext(); //key + parts.MoveNext(); //description + + var end = span[parts.Current.End.Value..]; + if (end.StartsWith(',')) + end = end[1..]; + + if (end.EndsWith(',')) + end = end[..^1]; + + var comma = start.IsEmpty || end.IsEmpty ? string.Empty : ","; + var result = $"{start}{comma}{end}"; + return result.Equals(string.Empty) ? null : result; + } + + parts.MoveNext(); //timestamp + parts.MoveNext(); //key + parts.MoveNext(); //description + } + + return secrets; + } + + public static bool ValidateLegacySecret(string secret, string comparand) + { + var bytes = Encoding.UTF8.GetBytes(secret); + var hash = SHA256.HashData(bytes); + var comparandValue = Convert.FromBase64String(comparand); + return CryptographicOperations.FixedTimeEquals(hash, comparandValue); + } + } +} diff --git a/SS14.Web/OpenId/TestDataSeeder.cs b/SS14.Web/OpenId/TestDataSeeder.cs new file mode 100644 index 0000000..9efc318 --- /dev/null +++ b/SS14.Web/OpenId/TestDataSeeder.cs @@ -0,0 +1,109 @@ +using System; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenIddict.Abstractions; +using SS14.Auth.Shared; +using SS14.Auth.Shared.Data; + +namespace SS14.Web.OpenId; + +public sealed class TestDataSeeder(IServiceProvider serviceProvider) : IHostedService +{ + public async Task StartAsync(CancellationToken ct) + { + using var scope = serviceProvider.CreateScope(); + await PopulateInternalApps(scope, ct); + await PopulateUsersAsync(scope); + } + + public Task StopAsync(CancellationToken ct) + { + return Task.CompletedTask; + } + + private async ValueTask PopulateInternalApps(IServiceScope scope, CancellationToken ct) + { + var appManager = scope.ServiceProvider.GetRequiredService(); + + var appDescriptor = new OpenIddictApplicationDescriptor + { + ClientId = "test_client", + ClientSecret = "test_secret", + ClientType = OpenIddictConstants.ClientTypes.Confidential, + DisplayName = "Test Client", + RedirectUris = { new Uri("https://localhost:5002/signin-oidc") }, + Requirements = { OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange }, + Settings = + { + {OpenIdConstants.SigningAlgorithmSetting, "RS256"}, + {OpenIdConstants.AllowPlainPkce, "true"}, + }, + Permissions = + { + OpenIddictConstants.Permissions.Endpoints.Authorization, + OpenIddictConstants.Permissions.Endpoints.Token, + OpenIddictConstants.Permissions.Endpoints.Introspection, + OpenIddictConstants.Permissions.Endpoints.EndSession, + OpenIddictConstants.Permissions.GrantTypes.RefreshToken, + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, + OpenIddictConstants.Permissions.ResponseTypes.Code, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Roles, + } + }; + + var client = await appManager.FindByClientIdAsync(appDescriptor.ClientId, ct); + if (client == null) + { + await appManager.CreateAsync(appDescriptor, ct); + } + else + { + await appManager.UpdateAsync(client, appDescriptor, ct); + } + } + + private async ValueTask PopulateUsersAsync(IServiceScope scope) + { + var userManager = scope.ServiceProvider.GetRequiredService>(); + var roleManager = scope.ServiceProvider.GetRequiredService>(); + + var sysAdmin = new SpaceRole + { + Name = AuthConstants.RoleSysAdmin, + }; + + await roleManager.CreateAsync(sysAdmin); + + var hubAdmin = new SpaceRole + { + Name = AuthConstants.RoleServerHubAdmin, + }; + + await roleManager.CreateAsync(hubAdmin); + + var user = new SpaceUser + { + UserName = "TestUser", + Email = "test@example.test", + EmailConfirmed = true, + }; + + // Logging throws an invalid operation exception if an account gets created by something like an I Hosted service + // for seeding data. Throwing here doesn't prevent creating the account so it gets logged as an error instead + // TODO: Ask if I can make the GetActorIP method log an error instead of throwing instead + try + { + await userManager.CreateAsync(user, "Test123456$"); + } + catch (InvalidOperationException e) { } + + await userManager.AddToRoleAsync(user, AuthConstants.RoleSysAdmin); + await userManager.AddToRoleAsync(user, AuthConstants.RoleServerHubAdmin); + } +} diff --git a/SS14.Web/OpenId/Types/AuthenticationValidationFailure.cs b/SS14.Web/OpenId/Types/AuthenticationValidationFailure.cs new file mode 100644 index 0000000..9309862 --- /dev/null +++ b/SS14.Web/OpenId/Types/AuthenticationValidationFailure.cs @@ -0,0 +1,16 @@ +#nullable enable +using Microsoft.AspNetCore.Authentication; +using OpenIddict.Abstractions; + +namespace SS14.Web.OpenId.Types; + +public record AuthenticationValidationFailure( + string? Error = null, + bool IsChallenge = false, + AuthenticationProperties? Properties = null) +{ + public static AuthenticationValidationFailure LoginRequired => new(Error: OpenIddictConstants.Errors.LoginRequired); + + public static AuthenticationValidationFailure Challenge (AuthenticationProperties properties) => + new(IsChallenge: true, Properties: properties); +}; diff --git a/SS14.Web/OpenId/Types/AuthorizationResult.cs b/SS14.Web/OpenId/Types/AuthorizationResult.cs new file mode 100644 index 0000000..d7e1116 --- /dev/null +++ b/SS14.Web/OpenId/Types/AuthorizationResult.cs @@ -0,0 +1,40 @@ +#nullable enable +using System.Collections.Immutable; +using System.Security.Claims; +using SS14.Auth.Shared.Data; + +namespace SS14.Web.OpenId.Types; + +public record AuthorizationResult( + AuthorizationResult.ResultType Type, + SpaceApplication? Application, + string? ErrorName, + ImmutableArray? Scopes, + ClaimsPrincipal? Principal) +{ + public static AuthorizationResult SignIn(ClaimsPrincipal principal) => + new(ResultType.SignIn, null, null, null, principal); + + public static AuthorizationResult Forbidden(SpaceApplication app, string error) + { + return new AuthorizationResult(ResultType.Forbidden, app, error, null, null); + } + + public static AuthorizationResult Consent(SpaceApplication app, ImmutableArray scopes) + { + return new AuthorizationResult(ResultType.Consent, app, null, scopes, null); + } + + public static AuthorizationResult Error(SpaceApplication? app, string error) + { + return new AuthorizationResult(ResultType.Error, app, error, null, null); + } + + public enum ResultType + { + SignIn, + Forbidden, + Consent, + Error, + } +} diff --git a/SS14.Web/OpenId/Types/ConsentResult.cs b/SS14.Web/OpenId/Types/ConsentResult.cs new file mode 100644 index 0000000..547e87f --- /dev/null +++ b/SS14.Web/OpenId/Types/ConsentResult.cs @@ -0,0 +1,18 @@ +#nullable enable +using System.Security.Claims; + +namespace SS14.Web.OpenId.Types; + +public record ConsentResult(ConsentResult.ResultType Type, string? ErrorName, ClaimsPrincipal? Principal) +{ + public static ConsentResult Error(string error) => new(ResultType.Error, error, null); + public static ConsentResult Forbid(string error) => new(ResultType.Forbid, error, null); + public static ConsentResult SignIn(ClaimsPrincipal principal) => new(ResultType.SignIn, null, principal); + + public enum ResultType + { + Error, + Forbid, + SignIn + } +} diff --git a/SS14.Web/PersonalDataCollector.cs b/SS14.Web/PersonalDataCollector.cs index 21d35ca..ff3b9e2 100644 --- a/SS14.Web/PersonalDataCollector.cs +++ b/SS14.Web/PersonalDataCollector.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.IO; using System.IO.Compression; using System.Linq; @@ -6,11 +7,13 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Dapper; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using OpenIddict.Core; using SS14.Auth.Shared.Data; +using SS14.Web.Extensions; +using SS14.Web.OpenId.Extensions; using SS14.WebEverythingShared; namespace SS14.Web; @@ -18,7 +21,12 @@ namespace SS14.Web; /// /// Helper class that wraps up all the personal data for a user into a neat little zip file! /// -public sealed class PersonalDataCollector(ApplicationDbContext dbContext, ILogger logger) +public sealed class PersonalDataCollector( + ApplicationDbContext dbContext, + OpenIddictApplicationManager applicationManager, + OpenIddictAuthorizationManager authorizationManager, + OpenIddictTokenManager tokenManager, + ILogger logger) { private static readonly JsonSerializerOptions JsonOptions = new() { @@ -41,7 +49,8 @@ public async Task CollectPersonalData(SpaceUser user, CancellationToken await CollectPastAccountNames(zip, user, cancel); await CollectPatrons(zip, user, cancel); await CollectUserOAuthClients(zip, user, cancel); - await CollectIS4PersistedGrants(zip, user, cancel); + await CollectAuthorizations(zip, user, cancel); + await CollectTokens(zip, user, cancel); await CollectHwidUsers(zip, user, cancel); } @@ -128,52 +137,68 @@ private async Task CollectPatrons(ZipArchive zip, SpaceUser user, CancellationTo private async Task CollectUserOAuthClients(ZipArchive zip, SpaceUser user, CancellationToken cancel) { - var dbConnection = dbContext.Database.GetDbConnection(); - var data = await dbConnection.QuerySingleAsync(""" - SELECT - COALESCE(json_agg(to_jsonb(data)), '[]') #>> '{}' - FROM ( - SELECT - *, - (SELECT to_jsonb(client) FROM ( - SELECT - *, - (SELECT COALESCE(json_agg(to_jsonb(client_scopeq)), '[]') FROM ( - SELECT * FROM "IS4"."ClientScopes" cs WHERE cs."ClientId" = c."Id" - ) client_scopeq) - as "Scopes", - (SELECT COALESCE(json_agg(to_jsonb(client_redirectq)), '[]') FROM ( - SELECT * FROM "IS4"."ClientRedirectUris" cru WHERE cru."ClientId" = c."Id" - ) client_redirectq) - as "RedirectUris", - (SELECT COALESCE(json_agg(to_jsonb(client_grantq)), '[]') FROM ( - SELECT * FROM "IS4"."ClientGrantTypes" cgt WHERE cgt."ClientId" = c."Id" - ) client_grantq) - as "GrantTypes" - FROM - "IS4"."Clients" c - WHERE c."Id" = a."ClientId" - ) client) - as "Client" - FROM - "UserOAuthClients" a - WHERE - "SpaceUserId" = @UserId - ) as data - """, - new - { - UserId = user.Id, - }); + var applications = await applicationManager.FindApplicationsByUserId(user.Id, cancel); + + var data = applications.Select(x => new SpaceApplicationData( + Id: x.Id, + UserId: x.SpaceUserId, + ClientId: x.ClientId, + ClientType: x.ClientType, + ApplicationType: x.ApplicationType, + LogoUri: x.LogoUri, + DisplayName: x.DisplayName, + Permissions: x.Permissions, + PostLogoutRedirectUris: x.PostLogoutRedirectUris, + RedirectUris: x.RedirectUris, + Properties: x.Properties, + Requirements: x.Requirements, + Settings: x.Settings, + HomePageUrl: x.WebsiteUrl + )); + + SerializeToFile(zip, "OAuthClients.json", data); + } + + private async Task CollectAuthorizations(ZipArchive zip, SpaceUser user, CancellationToken cancel) + { + var authorizations = await authorizationManager.FindBySubjectAsync(user.Id.ToString(), cancel) + .ToListAsync(cancellationToken: cancel); + + var data = authorizations.Select(x => new AuthorizationData( + Id: x.Id, + Subject: x.Subject, + ApplicationId: x.Application?.Id, + ApplicationName: x.Application?.DisplayName, + Type: x.Type, + Status: x.Status, + CreationDate: x.CreationDate, + Scopes: x.Scopes + )); - StringToFile(zip, "UserOAuthClients.json", data); + SerializeToFile(zip, "OAuthAuthorizations.json", data); } - private async Task CollectIS4PersistedGrants(ZipArchive zip, SpaceUser user, CancellationToken cancel) + private async Task CollectTokens(ZipArchive zip, SpaceUser user, CancellationToken cancel) { - var grants = await dbContext.PersistedGrants.Where(x => x.SubjectId == user.Id.ToString()).ToListAsync(cancel); + var tokens = await tokenManager.FindBySubjectAsync(user.Id.ToString(), cancel) + .ToListAsync(cancellationToken: cancel); - SerializeToFile(zip, "IS4.PersistedGrants.json", grants); + var data = tokens.Select(x => new TokenData( + Id: x.Id, + Subject: x.Subject, + AuthorizationId: x.Authorization?.Id, + ApplicationId: x.Application?.Id, + ApplicationName: x.Application?.DisplayName, + Status: x.Status, + Type: x.Type, + ReferenceId: x.ReferenceId, + Payload: x.Payload, + CreationDate: x.CreationDate, + ExpirationDate: x.ExpirationDate, + RedemptionDate: x.RedemptionDate + )); + + SerializeToFile(zip, "OAuthTokens.json", data); } private async Task CollectHwidUsers(ZipArchive zip, SpaceUser user, CancellationToken cancel) @@ -203,13 +228,58 @@ private static void SerializeToFile(ZipArchive archive, string name, T data) [UsedImplicitly] private sealed record SpaceUserData( Guid Id, - string UserName, + string? UserName, DateTimeOffset CreatedTime, bool EmailConfirmed, - string NormalizedEmail, + string? NormalizedEmail, bool TwoFactorEnabled, - string NormalizedUserName, + string? NormalizedUserName, bool AdminLocked, string AdminNotes, DateTimeOffset? LastUsernameChange); + + [UsedImplicitly] + private sealed record SpaceApplicationData( + string? Id, + Guid? UserId, + string? ClientId, + string? ClientType, + string? ApplicationType, + string? LogoUri, + string? DisplayName, + string? Permissions, + string? PostLogoutRedirectUris, + string? RedirectUris, + string? HomePageUrl, + string? Properties, + string? Requirements, + string? Settings); + + [UsedImplicitly] + private sealed record AuthorizationData( + string? Id, + string? Subject, + string? ApplicationId, + string? ApplicationName, + string? Type, + string? Status, + DateTime? CreationDate, + string? Scopes + ); + + [UsedImplicitly] + private sealed record TokenData( + string? Id, + string? Subject, + string? AuthorizationId, + string? ApplicationId, + string? ApplicationName, + string? Status, + string? Type, + string? ReferenceId, + string? Payload, + DateTime? CreationDate, + DateTime? ExpirationDate, + DateTime? RedemptionDate + ); } diff --git a/SS14.Web/Program.cs b/SS14.Web/Program.cs index 035b9b2..dd930ad 100644 --- a/SS14.Web/Program.cs +++ b/SS14.Web/Program.cs @@ -1,48 +1,154 @@ -using System.Linq; -using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Hosting; +#nullable enable +using System; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Rewrite; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Prometheus; +using Quartz; using Serilog; using SS14.Auth.Shared; +using SS14.ServerHub.Shared.Data; +using SS14.Web; +using SS14.Web.Data; +using SS14.Web.Extensions; +using SS14.Web.HCaptcha; +using SS14.Web.OpenId.Extensions; +using SS14.WebEverythingShared; -[assembly: InternalsVisibleTo("SS14.Web.Tests")] +var builder = WebApplication.CreateBuilder(); -namespace SS14.Web; +// Configuration +var env = builder.Environment; +builder.Configuration.AddYamlFile("appsettings.yml", false, true); +builder.Configuration.AddYamlFile($"appsettings.{env.EnvironmentName}.yml", true, true); +builder.Configuration.AddYamlFile("appsettings.Secret.yml", true, true); -public class Program +builder.Services.Configure(builder.Configuration.GetSection("Account")); + +// Logging +builder.Services.AddSerilog(config => +{ + config.ReadFrom.Configuration(builder.Configuration); + StartupHelpers.SetupLoki(config, builder.Configuration, "SS14.Web"); +}); + +builder.Services.AddScoped(); + +// Database + +builder.Services.AddDbContext(options => +{ + var connectionString = builder.Configuration.GetConnectionString("HubConnection") + ?? throw new InvalidOperationException("Must set HubConnection"); + + options.UseNpgsql(connectionString); +}); + +// Auth + +builder.Services.AddAuthorizationBuilder() + .AddPolicy(AuthConstants.PolicyAnyHubAdmin, policy => policy.RequireRole(AuthConstants.RoleSysAdmin, AuthConstants.RoleServerHubAdmin) ) + .AddPolicy(AuthConstants.PolicySysAdmin, policy => policy.RequireRole(AuthConstants.RoleSysAdmin) ) + .AddPolicy(AuthConstants.PolicyServerHubAdmin, policy => policy.RequireRole(AuthConstants.RoleServerHubAdmin) ); + +builder.Services.ConfigureApplicationCookie(options => +{ + options.LoginPath = $"/Identity/Account/Login"; + options.LogoutPath = $"/Identity/Account/Logout"; + options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; +}); + +// MVC + +builder.Services.AddMvc().AddRazorPagesOptions(options => +{ + options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage"); + options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout"); + options.Conventions.AuthorizeAreaFolder("Admin", "/", AuthConstants.PolicyAnyHubAdmin); + options.Conventions.AuthorizeAreaFolder("Admin", "/Clients", AuthConstants.PolicySysAdmin); + options.Conventions.AuthorizeAreaFolder("Admin", "/Users", AuthConstants.PolicySysAdmin); + options.Conventions.AuthorizeAreaFolder("Admin", "/Servers", AuthConstants.PolicyServerHubAdmin); +}); + +builder.Services.AddControllersWithViews(); +builder.Services.AddRazorPages(); + +// Services +builder.Services.AddSystemd(); +HCaptchaService.RegisterServices(builder.Services, builder.Configuration); +builder.AddPatreon(); +builder.AddOpenIdConnect(); +builder.Services.AddScoped(); +builder.AddShared(); + +// Required for openiddict pruning jobs +builder.Services.AddQuartz(o => { - public static void Main(string[] args) + o.UseSimpleTypeLoader(); + o.UseInMemoryStore(); +}); + +builder.Services.AddQuartzHostedService(o => o.WaitForJobsToComplete = true); + +builder.Services.Configure(IdentityConstants.ApplicationScheme, + options => { - CreateHostBuilder(args).Build().Run(); - } + options.LoginPath = $"/Identity/Account/Login"; + options.LogoutPath = $"/Identity/Account/Logout"; + options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; + }); + +var app = builder.Build(); - public static IHostBuilder CreateHostBuilder(string[] args) +app.UseSerilogRequestLogging(options => +{ + options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms to {ClientAddress}"; + options.EnrichDiagnosticContext = (context, httpContext) => { - return Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, builder) => - { - var env = context.HostingEnvironment; - builder.AddYamlFile("appsettings.yml", false, true); - builder.AddYamlFile($"appsettings.{env.EnvironmentName}.yml", true, true); - builder.AddYamlFile("appsettings.Secret.yml", true, true); - }) - .UseSerilog((ctx, cfg) => - { - cfg.ReadFrom.Configuration(ctx.Configuration); - - StartupHelpers.SetupLoki(cfg, ctx.Configuration, "SS14.Web"); - }) - .ConfigureWebHostDefaults(webBuilder => - { - var webRoot = webBuilder.GetSetting("WEBROOT"); - if (!string.IsNullOrEmpty(webRoot)) - { - webBuilder.UseWebRoot(webRoot); - } - - webBuilder.UseStartup(); - }) - .UseSystemd(); - } -} \ No newline at end of file + if (httpContext.Connection.RemoteIpAddress != null) + context.Set("ClientAddress", httpContext.Connection.RemoteIpAddress); + }; +}); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); + app.UseMigrationsEndPoint(); +} +else +{ + app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +MoreStartupHelpers.AddForwardedSupport(app, app.Configuration); + +var pathBase = app.Configuration.GetValue("PathBase"); +if (!string.IsNullOrEmpty(pathBase)) + app.UsePathBase(pathBase); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.UseHttpMetrics(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapDefaultControllerRoute(); +app.MapRazorPages(); +app.MapMetrics(); + +var options = new RewriteOptions() + .AddRewrite("^connect/authorize", "/Identity/Account/Consent", skipRemainingRules: true); + +app.UseRewriter(options); + +app.Run(); diff --git a/SS14.Web/SS14.Web.csproj b/SS14.Web/SS14.Web.csproj index 1b5c315..5530677 100644 --- a/SS14.Web/SS14.Web.csproj +++ b/SS14.Web/SS14.Web.csproj @@ -1,27 +1,31 @@ - net6.0 - 12 + net10.0 - - - - - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + - + - + <_ContentIncludedByDefault Remove="Areas\Admin\Pages\Servers\BannedAddresses\Index.cshtml" /> diff --git a/SS14.Web/Startup.cs b/SS14.Web/Startup.cs deleted file mode 100644 index 270f92d..0000000 --- a/SS14.Web/Startup.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Security.Cryptography; -using IdentityServer4; -using IdentityServer4.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Tokens; -using Prometheus; -using Serilog; -using SS14.Auth.Shared; -using SS14.Auth.Shared.Config; -using SS14.Auth.Shared.Data; -using SS14.ServerHub.Shared.Data; -using SS14.Web.Data; -using SS14.Web.HCaptcha; -using SS14.WebEverythingShared; - -namespace SS14.Web; - -public class Startup -{ - public Startup(IConfiguration configuration, IWebHostEnvironment environment) - { - Configuration = configuration; - Environment = environment; - } - - public IConfiguration Configuration { get; } - public IWebHostEnvironment Environment { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddScoped(); - - services.Configure(Configuration.GetSection("Account")); - HCaptchaService.RegisterServices(services, Configuration); - - services.AddDatabaseDeveloperPageExceptionFilter(); - StartupHelpers.AddShared(services, Configuration); - - services.AddDbContext(options => - { - var connectionString = Configuration.GetConnectionString("HubConnection") ?? throw new InvalidOperationException("Must set HubConnection"); - options.UseNpgsql(connectionString); - }); - - services.AddAuthorization(options => - { - options.AddPolicy( - AuthConstants.PolicyAnyHubAdmin, - policy => policy.RequireRole(AuthConstants.RoleSysAdmin, AuthConstants.RoleServerHubAdmin) - ); - - options.AddPolicy( - AuthConstants.PolicySysAdmin, - policy => policy.RequireRole(AuthConstants.RoleSysAdmin) - ); - - options.AddPolicy( - AuthConstants.PolicyServerHubAdmin, - policy => policy.RequireRole(AuthConstants.RoleServerHubAdmin) - ); - }); - - services.AddMvc() - .AddRazorPagesOptions(options => - { - options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage"); - options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout"); - options.Conventions.AuthorizeAreaFolder("Admin", "/", AuthConstants.PolicyAnyHubAdmin); - options.Conventions.AuthorizeAreaFolder("Admin", "/Clients", AuthConstants.PolicySysAdmin); - options.Conventions.AuthorizeAreaFolder("Admin", "/Users", AuthConstants.PolicySysAdmin); - options.Conventions.AuthorizeAreaFolder("Admin", "/Servers", AuthConstants.PolicyServerHubAdmin); - }); - - services.ConfigureApplicationCookie(options => - { - options.LoginPath = $"/Identity/Account/Login"; - options.LogoutPath = $"/Identity/Account/Logout"; - options.AccessDeniedPath = $"/Identity/Account/AccessDenied"; - }); - - services.AddControllersWithViews(); - services.AddRazorPages(); - - services.AddScoped(); - - var patreonSection = Configuration.GetSection("Patreon"); - var patreonCfg = patreonSection.Get(); - - services.AddAuthentication(options => options.DefaultScheme = IdentityConstants.ApplicationScheme); - - if (patreonCfg?.ClientId != null && patreonCfg.ClientSecret != null) - { - services.AddAuthentication() - // Rider is dumb that null is valid. - // It disables Patreon as an external login. - .AddPatreon("Patreon", null!, options => - { - // Patreon docs lied you don't need this to see memberships to your own campaign. - // options.Scope.Add("identity.memberships"); - options.Includes.Add("memberships.currently_entitled_tiers"); - options.ClientId = patreonCfg.ClientId; - options.ClientSecret = patreonCfg.ClientSecret; - - options.Events.OnCreatingTicket += context => - { - var handler = context.HttpContext.RequestServices.GetService(); - return handler!.HookCreatingTicket(context); - }; - - options.Events.OnTicketReceived += context => - { - var handler = context.HttpContext.RequestServices.GetService(); - return handler!.HookReceivedTicket(context); - }; - }); - } - - - var builder = services.AddIdentityServer(options => - { - options.UserInteraction.ConsentUrl = "/Identity/Account/Consent"; - }) - .AddAspNetIdentity() - .AddOperationalStore() - .AddConfigurationStore() - .AddInMemoryIdentityResources(new IdentityResource[] - { - new IdentityResources.OpenId(), - new IdentityResources.Profile(), - new IdentityResources.Email(), - }); - - var keyPath = Configuration.GetValue("Is4SigningKeyPath"); - if (keyPath == null) - { - if (Environment.IsDevelopment()) - { - Log.Debug("Using developer signing credentials"); - builder.AddDeveloperSigningCredential(); - } - else - { - throw new Exception("No key specified for IS4!"); - } - } - else - { - var keyPem = File.ReadAllText(keyPath); - var key = ECDsa.Create(); - key.ImportFromPem(keyPem); - - builder.AddSigningCredential( - new ECDsaSecurityKey(key), - IdentityServerConstants.ECDsaSigningAlgorithm.ES256); - } - - var keyPathRsa = Configuration.GetValue("Is4SigningKeyPathRsa"); - if (keyPathRsa != null) - { - var keyPem = File.ReadAllText(keyPathRsa); - var key = RSA.Create(); - key.ImportFromPem(keyPem); - - builder.AddSigningCredential( - new RsaSecurityKey(key), - IdentityServerConstants.RsaSigningAlgorithm.PS256); - builder.AddSigningCredential( - new RsaSecurityKey(key), - IdentityServerConstants.RsaSigningAlgorithm.RS256); - } - - services.AddScoped(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseSerilogRequestLogging(options => - { - options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms to {ClientAddress}"; - options.EnrichDiagnosticContext = (context, httpContext) => - { - context.Set("ClientAddress", httpContext.Connection.RemoteIpAddress); - }; - }); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseMigrationsEndPoint(); - } - else - { - app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - MoreStartupHelpers.AddForwardedSupport(app, Configuration); - - var pathBase = Configuration.GetValue("PathBase"); - if (!string.IsNullOrEmpty(pathBase)) - { - app.UsePathBase(pathBase); - } - - //app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseHttpMetrics(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - endpoints.MapRazorPages(); - endpoints.MapMetrics(); - }); - - app.UseIdentityServer(); - } -} diff --git a/SS14.Web/appsettings.Development.yml b/SS14.Web/appsettings.Development.yml index 872ffa2..2c7ed98 100644 --- a/SS14.Web/appsettings.Development.yml +++ b/SS14.Web/appsettings.Development.yml @@ -1,4 +1,13 @@ +Serilog: + Using: [ "Serilog.Sinks.Console", "Serilog.Sinks.Loki" ] + MinimumLevel: + Override: + OpenIddict: Information + + ConnectionStrings: DefaultConnection: "Server=127.0.0.1;Port=5432;Database=ss14-auth-test" urls: "https://localhost:5001" + +SeedTestData: true diff --git a/SS14.Web/appsettings.yml b/SS14.Web/appsettings.yml index 65336d0..6e18bc7 100644 --- a/SS14.Web/appsettings.yml +++ b/SS14.Web/appsettings.yml @@ -7,6 +7,7 @@ Serilog: Microsoft: "Warning" Microsoft.Hosting.Lifetime: "Information" Microsoft.AspNetCore: Warning + OpenIddict: Warning WriteTo: - Name: Console @@ -26,4 +27,17 @@ ForwardProxies: AllowedHosts: "*" Account: - UsernameChangeDays: 30 \ No newline at end of file + UsernameChangeDays: 30 + +OpenId: + Server: + AuthorizationEndpointUris: [ connect/authorize, Identity/Account/Consent ] + TokenEndpointUris: [ connect/token ] + EndSessionEndpointUris: [ connect/endsession ] + UserInfoEndpointUris: [ connect/userinfo ] + IntrospectionEndpointUris: [ connect/introspect ] + GrantTypes: + - authorization_code + - refresh_token + ResponseTypes: [ code ] + Scopes: [ openid, profile, email, roles, offline_access ] diff --git a/SS14.WebEverythingShared/SS14.WebEverythingShared.csproj b/SS14.WebEverythingShared/SS14.WebEverythingShared.csproj index 57b0055..81c54ca 100644 --- a/SS14.WebEverythingShared/SS14.WebEverythingShared.csproj +++ b/SS14.WebEverythingShared/SS14.WebEverythingShared.csproj @@ -1,7 +1,7 @@ - net6.0 + net10.0 enable enable 12 diff --git a/Tools/GenerateCerts.csx b/Tools/GenerateCerts.csx new file mode 100644 index 0000000..9db4d4a --- /dev/null +++ b/Tools/GenerateCerts.csx @@ -0,0 +1,35 @@ +#!/usr/bin/dotnet run +#:sdk Microsoft.NET.Sdk.Web + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using var rsaAlgorithm = RSA.Create(keySizeInBits: 2048); + +var subject = new X500DistinguishedName("CN=SpaceWizards"); + +// RSA Encryption Certificate +var request = new CertificateRequest(subject, rsaAlgorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); +request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); + +var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(10)); + +File.WriteAllBytes("server-encryption-certificate.pfx", certificate.Export(X509ContentType.Pfx, string.Empty)); + +// RSA Signing Certificate +request = new CertificateRequest(subject, rsaAlgorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); +request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); + +certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(10)); + +File.WriteAllBytes("server-signing-certificate-rsa.pfx", certificate.Export(X509ContentType.Pfx, string.Empty)); + +// RSA-PSS Signing Certificate +request = new CertificateRequest(subject, rsaAlgorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); +request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true)); + +certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(10)); + +File.WriteAllBytes("server-signing-certificate-rsa-pss.pfx", certificate.Export(X509ContentType.Pfx, string.Empty)); diff --git a/Tools/identityserver4_to_openiddict_data_migration.sql b/Tools/identityserver4_to_openiddict_data_migration.sql new file mode 100644 index 0000000..6662844 --- /dev/null +++ b/Tools/identityserver4_to_openiddict_data_migration.sql @@ -0,0 +1,26 @@ +insert into public."OpenIddictApplications" +select gen_random_uuid() as Id, + (select "SpaceUserId" from public."UserOAuthClients" uc where c."Id" = uc."ClientId"), + "LogoUri", null as ApplicationType, c."ClientId", + (select string_agg(array_to_string(array [ + cs."Id"::text, + floor(extract(epoch from cs."Created"))::text, + '_OLD_' || cs."Value", + replace(cs."Description", '*', '') + ], '.'), ',') from "IS4"."ClientSecrets" cs where cs."ClientId" = c."Id") ClientSecret, + 'confidential' as ClientType, gen_random_uuid() as ConcurrencyToken, + case when "RequireConsent" then 'Explicit' else 'Implicit' end as "ConsentType", + "ClientName" as DisplayName, null as DisplayNames, null as JsonWebKeySet, + '["ept:authorization","ept:token","ept:introspection","ept:end_session","gt:refresh_token","gt:authorization_code","rst:code","scp:email","scp:profile","scp:roles"]' + as Permissions, + null as PostLogoutRedirectUris, + null as Properties, + (select json_agg("RedirectUri") from "IS4"."ClientRedirectUris" cr where cr."ClientId" = c."Id")::text "RedirectUris", + case when "RequirePkce" then '["ft:pkce"]' end as "Requirements", + json_build_object( + 'space:AllowPlainPkce', "AllowPlainTextPkce"::TEXT, + 'space:SigningAlgorithm', "AllowedIdentityTokenSigningAlgorithms", + 'space:Disabled', ("Enabled" = false)::TEXT + )::text as "Settings", + "ClientUri" as WebsiteUrl +from "IS4"."Clients" c;