chat: add database types and auth middleware
This commit is contained in:
		
							parent
							
								
									656eec81d8
								
							
						
					
					
						commit
						6f6e19bbb5
					
				
					 24 changed files with 1165 additions and 15 deletions
				
			
		
							
								
								
									
										13
									
								
								.idea/.idea.Foxchat/.idea/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.idea/.idea.Foxchat/.idea/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Rider ignored files | ||||
| /.idea.Foxchat.iml | ||||
| /contentModel.xml | ||||
| /modules.xml | ||||
| /projectSettingsUpdater.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										1
									
								
								.idea/.idea.Foxchat/.idea/.name
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.idea/.idea.Foxchat/.idea/.name
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| Foxchat | ||||
							
								
								
									
										4
									
								
								.idea/.idea.Foxchat/.idea/encodings.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.idea/.idea.Foxchat/.idea/encodings.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" /> | ||||
| </project> | ||||
							
								
								
									
										8
									
								
								.idea/.idea.Foxchat/.idea/indexLayout.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.idea/.idea.Foxchat/.idea/indexLayout.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="UserContentModel"> | ||||
|     <attachedFolders /> | ||||
|     <explicitIncludes /> | ||||
|     <explicitExcludes /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										7
									
								
								.idea/.idea.Foxchat/.idea/sqldialects.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/.idea.Foxchat/.idea/sqldialects.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="SqlDialectMappings"> | ||||
|     <file url="file://$PROJECT_DIR$/Foxchat.Chat/Migrations/20240521132416_Init.cs" dialect="PostgreSQL" /> | ||||
|     <file url="PROJECT" dialect="PostgreSQL" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										6
									
								
								.idea/.idea.Foxchat/.idea/vcs.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/.idea.Foxchat/.idea/vcs.xml
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										6
									
								
								Foxchat.Chat/Database/BaseModel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								Foxchat.Chat/Database/BaseModel.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| namespace Foxchat.Chat.Database; | ||||
| 
 | ||||
| public abstract class BaseModel | ||||
| { | ||||
|     public Ulid Id { get; init; } = Ulid.NewUlid(); | ||||
| } | ||||
|  | @ -1,3 +1,4 @@ | |||
| using Foxchat.Chat.Database.Models; | ||||
| using Foxchat.Core; | ||||
| using Foxchat.Core.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | @ -11,6 +12,11 @@ public class ChatContext : IDatabaseContext | |||
|     private readonly NpgsqlDataSource _dataSource; | ||||
| 
 | ||||
|     public override DbSet<Instance> Instance { get; set; } | ||||
|     public DbSet<IdentityInstance> IdentityInstances { get; set; } | ||||
|     public DbSet<User> Users { get; set; } | ||||
|     public DbSet<Guild> Guilds { get; set; } | ||||
|     public DbSet<Channel> Channels { get; set; } | ||||
|     public DbSet<Message> Messages { get; set; } | ||||
| 
 | ||||
|     public ChatContext(InstanceConfig config) | ||||
|     { | ||||
|  | @ -38,16 +44,31 @@ public class ChatContext : IDatabaseContext | |||
| 
 | ||||
|     protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||
|     { | ||||
|         modelBuilder.Entity<IdentityInstance>().HasIndex(i => i.Domain).IsUnique(); | ||||
| 
 | ||||
|         modelBuilder.Entity<User>().HasIndex(u => new { u.RemoteUserId, u.InstanceId }).IsUnique(); | ||||
|         modelBuilder.Entity<User>().HasIndex(u => new { u.Username, u.InstanceId }).IsUnique(); | ||||
| 
 | ||||
|         modelBuilder.Entity<Guild>() | ||||
|             .HasOne(e => e.Owner) | ||||
|             .WithMany(e => e.OwnedGuilds) | ||||
|             .HasForeignKey(e => e.OwnerId) | ||||
|             .IsRequired(); | ||||
| 
 | ||||
|         modelBuilder.Entity<User>() | ||||
|             .HasMany(e => e.Guilds) | ||||
|             .WithMany(e => e.Users); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ReSharper disable once UnusedType.Global | ||||
| public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<ChatContext> | ||||
| { | ||||
|     public ChatContext CreateDbContext(string[] args) | ||||
|     { | ||||
|         // Read the configuration file | ||||
|         var config = new ConfigurationBuilder() | ||||
|             .AddConfiguration("identity.ini") | ||||
|             .AddConfiguration("chat.ini") | ||||
|             .Build() | ||||
|             // Get the configuration as our config class | ||||
|             .Get<InstanceConfig>() ?? new(); | ||||
|  |  | |||
							
								
								
									
										9
									
								
								Foxchat.Chat/Database/Models/Channel.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Foxchat.Chat/Database/Models/Channel.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| namespace Foxchat.Chat.Database.Models; | ||||
| 
 | ||||
| public class Channel : BaseModel | ||||
| { | ||||
|     public Ulid GuildId { get; init; } | ||||
|     public Guild Guild { get; init; } = null!; | ||||
|     public string Name { get; set; } = null!; | ||||
|     public string? Topic { get; set; } | ||||
| } | ||||
							
								
								
									
										11
									
								
								Foxchat.Chat/Database/Models/Guild.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Foxchat.Chat/Database/Models/Guild.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| namespace Foxchat.Chat.Database.Models; | ||||
| 
 | ||||
| public class Guild : BaseModel | ||||
| { | ||||
|     public string Name { get; set; } = null!; | ||||
|     public Ulid OwnerId { get; set; } | ||||
|     public User Owner { get; set; } = null!; | ||||
| 
 | ||||
|     public List<User> Users { get; } = []; | ||||
|     public List<Channel> Channels { get; } = []; | ||||
| } | ||||
							
								
								
									
										17
									
								
								Foxchat.Chat/Database/Models/IdentityInstance.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Foxchat.Chat/Database/Models/IdentityInstance.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| namespace Foxchat.Chat.Database.Models; | ||||
| 
 | ||||
| public class IdentityInstance : BaseModel | ||||
| { | ||||
|     public string Domain { get; init; } = null!; | ||||
|     public string BaseUrl { get; init; } = null!; | ||||
|     public string PublicKey { get; init; } = null!; | ||||
| 
 | ||||
|     public InstanceStatus Status { get; set; } = InstanceStatus.Active; | ||||
|     public string? Reason { get; set; } | ||||
| 
 | ||||
|     public enum InstanceStatus | ||||
|     { | ||||
|         Active, | ||||
|         Suspended, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								Foxchat.Chat/Database/Models/Message.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Foxchat.Chat/Database/Models/Message.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| using NodaTime; | ||||
| 
 | ||||
| namespace Foxchat.Chat.Database.Models; | ||||
| 
 | ||||
| public class Message : BaseModel | ||||
| { | ||||
|     public Ulid ChannelId { get; init; } | ||||
|     public Channel Channel { get; init; } = null!; | ||||
|     public Ulid AuthorId { get; init; } | ||||
|     public User Author { get; init; } = null!; | ||||
|      | ||||
|     public string? Content { get; set; } | ||||
|      | ||||
|     public Instant? UpdatedAt { get; set; } | ||||
| } | ||||
							
								
								
									
										14
									
								
								Foxchat.Chat/Database/Models/User.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Foxchat.Chat/Database/Models/User.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| namespace Foxchat.Chat.Database.Models; | ||||
| 
 | ||||
| public class User : BaseModel | ||||
| { | ||||
|     public Ulid InstanceId { get; init; } | ||||
|     public IdentityInstance Instance { get; init; } = null!; | ||||
|     public string RemoteUserId { get; init; } = null!; | ||||
|     public string Username { get; init; } = null!; | ||||
| 
 | ||||
|     public string? Avatar { get; set; } | ||||
| 
 | ||||
|     public List<Guild> Guilds { get; } = []; | ||||
|     public List<Guild> OwnedGuilds { get; } = []; | ||||
| } | ||||
							
								
								
									
										18
									
								
								Foxchat.Chat/Extensions/WebApplicationExtensions.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Foxchat.Chat/Extensions/WebApplicationExtensions.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| using Foxchat.Chat.Middleware; | ||||
| 
 | ||||
| namespace Foxchat.Chat.Extensions; | ||||
| 
 | ||||
| public static class WebApplicationExtensions | ||||
| { | ||||
|     public static IServiceCollection AddCustomMiddleware(this IServiceCollection services) | ||||
|     { | ||||
|         return services | ||||
|             .AddScoped<AuthenticationMiddleware>(); | ||||
|     } | ||||
| 
 | ||||
|     public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app) | ||||
|     { | ||||
|         return app | ||||
|             .UseMiddleware<AuthenticationMiddleware>(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										106
									
								
								Foxchat.Chat/Middleware/AuthenticationMiddleware.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								Foxchat.Chat/Middleware/AuthenticationMiddleware.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,106 @@ | |||
| using Foxchat.Chat.Database; | ||||
| using Foxchat.Chat.Database.Models; | ||||
| using Foxchat.Core; | ||||
| using Foxchat.Core.Federation; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| 
 | ||||
| namespace Foxchat.Chat.Middleware; | ||||
| 
 | ||||
| public class AuthenticationMiddleware(ILogger logger, ChatContext db, RequestSigningService requestSigningService) | ||||
|     : IMiddleware | ||||
| { | ||||
|     public async Task InvokeAsync(HttpContext ctx, RequestDelegate next) | ||||
|     { | ||||
|         var endpoint = ctx.GetEndpoint(); | ||||
|         // Endpoints require server authentication by default, unless they have the [Unauthenticated] attribute. | ||||
|         var metadata = endpoint?.Metadata.GetMetadata<UnauthenticatedAttribute>(); | ||||
|         if (metadata != null) | ||||
|         { | ||||
|             await next(ctx); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!ExtractRequestData(ctx, out var signature, out var domain, out var signatureData)) | ||||
|             throw new ApiError.IncomingFederationError("This endpoint requires signed requests."); | ||||
| 
 | ||||
|         var instance = await GetInstanceAsync(domain); | ||||
| 
 | ||||
|         if (!requestSigningService.VerifySignature(instance.PublicKey, signature, signatureData)) | ||||
|             throw new ApiError.IncomingFederationError("Signature is not valid."); | ||||
|          | ||||
|         ctx.SetSignature(instance, signatureData); | ||||
| 
 | ||||
|         await next(ctx); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<IdentityInstance> GetInstanceAsync(string domain) | ||||
|     { | ||||
|         return await db.IdentityInstances.FirstOrDefaultAsync(i => i.Domain == domain) | ||||
|                ?? throw new ApiError.IncomingFederationError("Remote instance is not known."); | ||||
|     } | ||||
| 
 | ||||
|     private bool ExtractRequestData(HttpContext ctx, out string signature, out string domain, out SignatureData data) | ||||
|     { | ||||
|         signature = string.Empty; | ||||
|         domain = string.Empty; | ||||
|         data = SignatureData.Empty; | ||||
| 
 | ||||
|         if (!ctx.Request.Headers.TryGetValue(RequestSigningService.SIGNATURE_HEADER, out var encodedSignature)) | ||||
|             return false; | ||||
|         if (!ctx.Request.Headers.TryGetValue(RequestSigningService.DATE_HEADER, out var date)) | ||||
|             return false; | ||||
|         if (!ctx.Request.Headers.TryGetValue(RequestSigningService.SERVER_HEADER, out var server)) | ||||
|             return false; | ||||
|         var time = RequestSigningService.ParseTime(date.ToString()); | ||||
|         string? userId = null; | ||||
|         if (ctx.Request.Headers.TryGetValue(RequestSigningService.USER_HEADER, out var userIdHeader)) | ||||
|             userId = userIdHeader; | ||||
|         var host = ctx.Request.Headers.Host.ToString(); | ||||
| 
 | ||||
|         signature = encodedSignature.ToString(); | ||||
|         domain = server.ToString(); | ||||
|         data = new SignatureData( | ||||
|             time, | ||||
|             host, | ||||
|             ctx.Request.Path, | ||||
|             (int?)ctx.Request.Headers.ContentLength, | ||||
|             userId | ||||
|         ); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] | ||||
| public class UnauthenticatedAttribute : Attribute; | ||||
| 
 | ||||
| public static class HttpContextExtensions | ||||
| { | ||||
|     private const string Key = "instance"; | ||||
| 
 | ||||
|     public static void SetSignature(this HttpContext ctx, IdentityInstance instance, SignatureData data) | ||||
|     { | ||||
|         ctx.Items.Add(Key, (instance, data)); | ||||
|     } | ||||
| 
 | ||||
|     public static (IdentityInstance?, SignatureData?) GetSignature(this HttpContext ctx) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var obj = ctx.GetSignatureOrThrow(); | ||||
|             return (obj.Item1, obj.Item2); | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             return (null, null); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static (IdentityInstance, SignatureData) GetSignatureOrThrow(this HttpContext ctx) | ||||
|     { | ||||
|         if (!ctx.Items.TryGetValue(Key, out var obj)) | ||||
|             throw new ApiError.AuthenticationError("No instance in HttpContext"); | ||||
| 
 | ||||
|         return ((IdentityInstance, SignatureData))obj!; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										325
									
								
								Foxchat.Chat/Migrations/20240521132416_Init.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								Foxchat.Chat/Migrations/20240521132416_Init.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,325 @@ | |||
| // <auto-generated /> | ||||
| using System; | ||||
| using Foxchat.Chat.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace Foxchat.Chat.Migrations | ||||
| { | ||||
|     [DbContext(typeof(ChatContext))] | ||||
|     [Migration("20240521132416_Init")] | ||||
|     partial class Init | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "8.0.5") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Channel", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("GuildId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("guild_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<string>("Topic") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("topic"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_channels"); | ||||
| 
 | ||||
|                     b.HasIndex("GuildId") | ||||
|                         .HasDatabaseName("ix_channels_guild_id"); | ||||
| 
 | ||||
|                     b.ToTable("channels", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<Guid>("OwnerId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("owner_id"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_guilds"); | ||||
| 
 | ||||
|                     b.HasIndex("OwnerId") | ||||
|                         .HasDatabaseName("ix_guilds_owner_id"); | ||||
| 
 | ||||
|                     b.ToTable("guilds", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.IdentityInstance", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("BaseUrl") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("base_url"); | ||||
| 
 | ||||
|                     b.Property<string>("Domain") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("domain"); | ||||
| 
 | ||||
|                     b.Property<string>("PublicKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("public_key"); | ||||
| 
 | ||||
|                     b.Property<string>("Reason") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("reason"); | ||||
| 
 | ||||
|                     b.Property<int>("Status") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_identity_instances"); | ||||
| 
 | ||||
|                     b.HasIndex("Domain") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_identity_instances_domain"); | ||||
| 
 | ||||
|                     b.ToTable("identity_instances", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Message", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("AuthorId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("author_id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("ChannelId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("channel_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Content") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("content"); | ||||
| 
 | ||||
|                     b.Property<Instant?>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_messages"); | ||||
| 
 | ||||
|                     b.HasIndex("AuthorId") | ||||
|                         .HasDatabaseName("ix_messages_author_id"); | ||||
| 
 | ||||
|                     b.HasIndex("ChannelId") | ||||
|                         .HasDatabaseName("ix_messages_channel_id"); | ||||
| 
 | ||||
|                     b.ToTable("messages", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("Avatar") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("avatar"); | ||||
| 
 | ||||
|                     b.Property<Guid>("InstanceId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("instance_id"); | ||||
| 
 | ||||
|                     b.Property<string>("RemoteUserId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("remote_user_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Username") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("username"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_users"); | ||||
| 
 | ||||
|                     b.HasIndex("InstanceId") | ||||
|                         .HasDatabaseName("ix_users_instance_id"); | ||||
| 
 | ||||
|                     b.HasIndex("RemoteUserId", "InstanceId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_remote_user_id_instance_id"); | ||||
| 
 | ||||
|                     b.HasIndex("Username", "InstanceId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_username_instance_id"); | ||||
| 
 | ||||
|                     b.ToTable("users", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Core.Database.Instance", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<string>("PrivateKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("private_key"); | ||||
| 
 | ||||
|                     b.Property<string>("PublicKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("public_key"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_instance"); | ||||
| 
 | ||||
|                     b.ToTable("instance", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("GuildUser", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("GuildsId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("guilds_id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("UsersId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("users_id"); | ||||
| 
 | ||||
|                     b.HasKey("GuildsId", "UsersId") | ||||
|                         .HasName("pk_guild_user"); | ||||
| 
 | ||||
|                     b.HasIndex("UsersId") | ||||
|                         .HasDatabaseName("ix_guild_user_users_id"); | ||||
| 
 | ||||
|                     b.ToTable("guild_user", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Channel", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Guild", "Guild") | ||||
|                         .WithMany("Channels") | ||||
|                         .HasForeignKey("GuildId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_channels_guilds_guild_id"); | ||||
| 
 | ||||
|                     b.Navigation("Guild"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", "Owner") | ||||
|                         .WithMany("OwnedGuilds") | ||||
|                         .HasForeignKey("OwnerId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guilds_users_owner_id"); | ||||
| 
 | ||||
|                     b.Navigation("Owner"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Message", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", "Author") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AuthorId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_messages_users_author_id"); | ||||
| 
 | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Channel", "Channel") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("ChannelId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_messages_channels_channel_id"); | ||||
| 
 | ||||
|                     b.Navigation("Author"); | ||||
| 
 | ||||
|                     b.Navigation("Channel"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.IdentityInstance", "Instance") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("InstanceId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_users_identity_instances_instance_id"); | ||||
| 
 | ||||
|                     b.Navigation("Instance"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("GuildUser", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Guild", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("GuildsId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guild_user_guilds_guilds_id"); | ||||
| 
 | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("UsersId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guild_user_users_users_id"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.Navigation("Channels"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.Navigation("OwnedGuilds"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										228
									
								
								Foxchat.Chat/Migrations/20240521132416_Init.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								Foxchat.Chat/Migrations/20240521132416_Init.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,228 @@ | |||
| using System; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace Foxchat.Chat.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class Init : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "identity_instances", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     domain = table.Column<string>(type: "text", nullable: false), | ||||
|                     base_url = table.Column<string>(type: "text", nullable: false), | ||||
|                     public_key = table.Column<string>(type: "text", nullable: false), | ||||
|                     status = table.Column<int>(type: "integer", nullable: false), | ||||
|                     reason = table.Column<string>(type: "text", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_identity_instances", x => x.id); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "instance", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<int>(type: "integer", nullable: false) | ||||
|                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), | ||||
|                     public_key = table.Column<string>(type: "text", nullable: false), | ||||
|                     private_key = table.Column<string>(type: "text", nullable: false) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_instance", x => x.id); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "users", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     instance_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     remote_user_id = table.Column<string>(type: "text", nullable: false), | ||||
|                     username = table.Column<string>(type: "text", nullable: false), | ||||
|                     avatar = table.Column<string>(type: "text", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_users", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_users_identity_instances_instance_id", | ||||
|                         column: x => x.instance_id, | ||||
|                         principalTable: "identity_instances", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "guilds", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     name = table.Column<string>(type: "text", nullable: false), | ||||
|                     owner_id = table.Column<Guid>(type: "uuid", nullable: false) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_guilds", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_guilds_users_owner_id", | ||||
|                         column: x => x.owner_id, | ||||
|                         principalTable: "users", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "channels", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     guild_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     name = table.Column<string>(type: "text", nullable: false), | ||||
|                     topic = table.Column<string>(type: "text", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_channels", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_channels_guilds_guild_id", | ||||
|                         column: x => x.guild_id, | ||||
|                         principalTable: "guilds", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "guild_user", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     guilds_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     users_id = table.Column<Guid>(type: "uuid", nullable: false) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_guild_user", x => new { x.guilds_id, x.users_id }); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_guild_user_guilds_guilds_id", | ||||
|                         column: x => x.guilds_id, | ||||
|                         principalTable: "guilds", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_guild_user_users_users_id", | ||||
|                         column: x => x.users_id, | ||||
|                         principalTable: "users", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateTable( | ||||
|                 name: "messages", | ||||
|                 columns: table => new | ||||
|                 { | ||||
|                     id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     channel_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     author_id = table.Column<Guid>(type: "uuid", nullable: false), | ||||
|                     content = table.Column<string>(type: "text", nullable: true), | ||||
|                     updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true) | ||||
|                 }, | ||||
|                 constraints: table => | ||||
|                 { | ||||
|                     table.PrimaryKey("pk_messages", x => x.id); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_messages_channels_channel_id", | ||||
|                         column: x => x.channel_id, | ||||
|                         principalTable: "channels", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                     table.ForeignKey( | ||||
|                         name: "fk_messages_users_author_id", | ||||
|                         column: x => x.author_id, | ||||
|                         principalTable: "users", | ||||
|                         principalColumn: "id", | ||||
|                         onDelete: ReferentialAction.Cascade); | ||||
|                 }); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_channels_guild_id", | ||||
|                 table: "channels", | ||||
|                 column: "guild_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_guild_user_users_id", | ||||
|                 table: "guild_user", | ||||
|                 column: "users_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_guilds_owner_id", | ||||
|                 table: "guilds", | ||||
|                 column: "owner_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_identity_instances_domain", | ||||
|                 table: "identity_instances", | ||||
|                 column: "domain", | ||||
|                 unique: true); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_messages_author_id", | ||||
|                 table: "messages", | ||||
|                 column: "author_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_messages_channel_id", | ||||
|                 table: "messages", | ||||
|                 column: "channel_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_users_instance_id", | ||||
|                 table: "users", | ||||
|                 column: "instance_id"); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_users_remote_user_id_instance_id", | ||||
|                 table: "users", | ||||
|                 columns: new[] { "remote_user_id", "instance_id" }, | ||||
|                 unique: true); | ||||
| 
 | ||||
|             // EF Core doesn't support creating indexes on arbitrary expressions, so we have to create it manually. | ||||
|             migrationBuilder.Sql("CREATE UNIQUE INDEX ix_users_username_instance_id ON users (lower(username), instance_id)"); | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "guild_user"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "instance"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "messages"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "channels"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "guilds"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "users"); | ||||
| 
 | ||||
|             migrationBuilder.DropTable( | ||||
|                 name: "identity_instances"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										322
									
								
								Foxchat.Chat/Migrations/ChatContextModelSnapshot.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								Foxchat.Chat/Migrations/ChatContextModelSnapshot.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,322 @@ | |||
| // <auto-generated /> | ||||
| using System; | ||||
| using Foxchat.Chat.Database; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||
| using NodaTime; | ||||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace Foxchat.Chat.Migrations | ||||
| { | ||||
|     [DbContext(typeof(ChatContext))] | ||||
|     partial class ChatContextModelSnapshot : ModelSnapshot | ||||
|     { | ||||
|         protected override void BuildModel(ModelBuilder modelBuilder) | ||||
|         { | ||||
| #pragma warning disable 612, 618 | ||||
|             modelBuilder | ||||
|                 .HasAnnotation("ProductVersion", "8.0.5") | ||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||
| 
 | ||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Channel", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("GuildId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("guild_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<string>("Topic") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("topic"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_channels"); | ||||
| 
 | ||||
|                     b.HasIndex("GuildId") | ||||
|                         .HasDatabaseName("ix_channels_guild_id"); | ||||
| 
 | ||||
|                     b.ToTable("channels", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("name"); | ||||
| 
 | ||||
|                     b.Property<Guid>("OwnerId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("owner_id"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_guilds"); | ||||
| 
 | ||||
|                     b.HasIndex("OwnerId") | ||||
|                         .HasDatabaseName("ix_guilds_owner_id"); | ||||
| 
 | ||||
|                     b.ToTable("guilds", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.IdentityInstance", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("BaseUrl") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("base_url"); | ||||
| 
 | ||||
|                     b.Property<string>("Domain") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("domain"); | ||||
| 
 | ||||
|                     b.Property<string>("PublicKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("public_key"); | ||||
| 
 | ||||
|                     b.Property<string>("Reason") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("reason"); | ||||
| 
 | ||||
|                     b.Property<int>("Status") | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("status"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_identity_instances"); | ||||
| 
 | ||||
|                     b.HasIndex("Domain") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_identity_instances_domain"); | ||||
| 
 | ||||
|                     b.ToTable("identity_instances", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Message", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("AuthorId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("author_id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("ChannelId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("channel_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Content") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("content"); | ||||
| 
 | ||||
|                     b.Property<Instant?>("UpdatedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("updated_at"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_messages"); | ||||
| 
 | ||||
|                     b.HasIndex("AuthorId") | ||||
|                         .HasDatabaseName("ix_messages_author_id"); | ||||
| 
 | ||||
|                     b.HasIndex("ChannelId") | ||||
|                         .HasDatabaseName("ix_messages_channel_id"); | ||||
| 
 | ||||
|                     b.ToTable("messages", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("Id") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     b.Property<string>("Avatar") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("avatar"); | ||||
| 
 | ||||
|                     b.Property<Guid>("InstanceId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("instance_id"); | ||||
| 
 | ||||
|                     b.Property<string>("RemoteUserId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("remote_user_id"); | ||||
| 
 | ||||
|                     b.Property<string>("Username") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("username"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_users"); | ||||
| 
 | ||||
|                     b.HasIndex("InstanceId") | ||||
|                         .HasDatabaseName("ix_users_instance_id"); | ||||
| 
 | ||||
|                     b.HasIndex("RemoteUserId", "InstanceId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_remote_user_id_instance_id"); | ||||
| 
 | ||||
|                     b.HasIndex("Username", "InstanceId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_username_instance_id"); | ||||
| 
 | ||||
|                     b.ToTable("users", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Core.Database.Instance", b => | ||||
|                 { | ||||
|                     b.Property<int>("Id") | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("integer") | ||||
|                         .HasColumnName("id"); | ||||
| 
 | ||||
|                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||
| 
 | ||||
|                     b.Property<string>("PrivateKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("private_key"); | ||||
| 
 | ||||
|                     b.Property<string>("PublicKey") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("public_key"); | ||||
| 
 | ||||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_instance"); | ||||
| 
 | ||||
|                     b.ToTable("instance", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("GuildUser", b => | ||||
|                 { | ||||
|                     b.Property<Guid>("GuildsId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("guilds_id"); | ||||
| 
 | ||||
|                     b.Property<Guid>("UsersId") | ||||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("users_id"); | ||||
| 
 | ||||
|                     b.HasKey("GuildsId", "UsersId") | ||||
|                         .HasName("pk_guild_user"); | ||||
| 
 | ||||
|                     b.HasIndex("UsersId") | ||||
|                         .HasDatabaseName("ix_guild_user_users_id"); | ||||
| 
 | ||||
|                     b.ToTable("guild_user", (string)null); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Channel", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Guild", "Guild") | ||||
|                         .WithMany("Channels") | ||||
|                         .HasForeignKey("GuildId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_channels_guilds_guild_id"); | ||||
| 
 | ||||
|                     b.Navigation("Guild"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", "Owner") | ||||
|                         .WithMany("OwnedGuilds") | ||||
|                         .HasForeignKey("OwnerId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guilds_users_owner_id"); | ||||
| 
 | ||||
|                     b.Navigation("Owner"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Message", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", "Author") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("AuthorId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_messages_users_author_id"); | ||||
| 
 | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Channel", "Channel") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("ChannelId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_messages_channels_channel_id"); | ||||
| 
 | ||||
|                     b.Navigation("Author"); | ||||
| 
 | ||||
|                     b.Navigation("Channel"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.IdentityInstance", "Instance") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("InstanceId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_users_identity_instances_instance_id"); | ||||
| 
 | ||||
|                     b.Navigation("Instance"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("GuildUser", b => | ||||
|                 { | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.Guild", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("GuildsId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guild_user_guilds_guilds_id"); | ||||
| 
 | ||||
|                     b.HasOne("Foxchat.Chat.Database.Models.User", null) | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("UsersId") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_guild_user_users_users_id"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.Guild", b => | ||||
|                 { | ||||
|                     b.Navigation("Channels"); | ||||
|                 }); | ||||
| 
 | ||||
|             modelBuilder.Entity("Foxchat.Chat.Database.Models.User", b => | ||||
|                 { | ||||
|                     b.Navigation("OwnedGuilds"); | ||||
|                 }); | ||||
| #pragma warning restore 612, 618 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -3,6 +3,7 @@ using Serilog; | |||
| using Foxchat.Core; | ||||
| using Foxchat.Chat; | ||||
| using Foxchat.Chat.Database; | ||||
| using Newtonsoft.Json; | ||||
| 
 | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
| 
 | ||||
|  | @ -13,6 +14,15 @@ builder.AddSerilog(config.LogEventLevel); | |||
| await BuildInfo.ReadBuildInfo(); | ||||
| Log.Information("Starting Foxchat.Chat {Version} ({Hash})", BuildInfo.Version, BuildInfo.Hash); | ||||
| 
 | ||||
| // Set the default converter to snake case as we use it in a couple places. | ||||
| JsonConvert.DefaultSettings = () => new JsonSerializerSettings | ||||
| { | ||||
|     ContractResolver = new DefaultContractResolver | ||||
|     { | ||||
|         NamingStrategy = new SnakeCaseNamingStrategy() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| builder.Services | ||||
|     .AddControllers() | ||||
|     .AddNewtonsoftJson(options => | ||||
|  | @ -38,7 +48,7 @@ app.UseAuthorization(); | |||
| app.MapControllers(); | ||||
| 
 | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| using (var context = scope.ServiceProvider.GetRequiredService<ChatContext>()) | ||||
| await using (var context = scope.ServiceProvider.GetRequiredService<ChatContext>()) | ||||
| { | ||||
|     Log.Information("Initializing instance keypair..."); | ||||
|     if (await context.InitializeInstanceAsync()) | ||||
|  |  | |||
|  | @ -31,23 +31,22 @@ public partial class RequestSigningService(ILogger logger, IClock clock, IDataba | |||
|     } | ||||
| 
 | ||||
|     public bool VerifySignature( | ||||
|         string publicKey, string encodedSignature, string dateHeader, string host, string requestPath, int? contentLength, string? userId) | ||||
|         string publicKey, string encodedSignature, SignatureData data) | ||||
|     { | ||||
|         var rsa = RSA.Create(); | ||||
|         rsa.ImportFromPem(publicKey); | ||||
| 
 | ||||
|         var now = _clock.GetCurrentInstant(); | ||||
|         var time = ParseTime(dateHeader); | ||||
|         if ((now + Duration.FromMinutes(1)) < time) | ||||
|         if ((now + Duration.FromMinutes(1)) < data.Time) | ||||
|         { | ||||
|             throw new ApiError.IncomingFederationError("Request was made in the future"); | ||||
|         } | ||||
|         else if ((now - Duration.FromMinutes(1)) > time) | ||||
|         else if ((now - Duration.FromMinutes(1)) > data.Time) | ||||
|         { | ||||
|             throw new ApiError.IncomingFederationError("Request was made too long ago"); | ||||
|         } | ||||
| 
 | ||||
|         var plaintext = GeneratePlaintext(new SignatureData(time, host, requestPath, contentLength, userId)); | ||||
|         var plaintext = GeneratePlaintext(data); | ||||
|         var plaintextBytes = Encoding.UTF8.GetBytes(plaintext); | ||||
|         var hash = SHA256.HashData(plaintextBytes); | ||||
| 
 | ||||
|  | @ -73,5 +72,5 @@ public partial class RequestSigningService(ILogger logger, IClock clock, IDataba | |||
| 
 | ||||
|     private static readonly InstantPattern _pattern = InstantPattern.Create("ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.GetCultureInfo("en-US")); | ||||
|     private static string FormatTime(Instant time) => _pattern.Format(time); | ||||
|     private static Instant ParseTime(string header) => _pattern.Parse(header).GetValueOrThrow(); | ||||
|     public static Instant ParseTime(string header) => _pattern.Parse(header).GetValueOrThrow(); | ||||
| } | ||||
|  |  | |||
|  | @ -8,4 +8,13 @@ public record SignatureData( | |||
|     string RequestPath, | ||||
|     int? ContentLength, | ||||
|     string? UserId | ||||
| ) | ||||
| { | ||||
|     public static readonly SignatureData Empty = new( | ||||
|         Instant.MinValue, | ||||
|         string.Empty, | ||||
|         string.Empty, | ||||
|         null, | ||||
|         null | ||||
|     ); | ||||
| } | ||||
|  | @ -11,9 +11,9 @@ public class IdentityContext : IDatabaseContext | |||
| { | ||||
|     private readonly NpgsqlDataSource _dataSource; | ||||
| 
 | ||||
|     public override DbSet<Instance> Instance { get; set; } | ||||
|     public DbSet<Account> Accounts { get; set; } | ||||
|     public DbSet<ChatInstance> ChatInstances { get; set; } | ||||
|     public override DbSet<Instance> Instance { get; set; } | ||||
|     public DbSet<Application> Applications { get; set; } | ||||
|     public DbSet<Token> Tokens { get; set; } | ||||
|     public DbSet<GuildAccount> GuildAccounts { get; set; } | ||||
|  | @ -55,6 +55,7 @@ public class IdentityContext : IDatabaseContext | |||
|     } | ||||
| } | ||||
| 
 | ||||
| // ReSharper disable once UnusedType.Global | ||||
| public class DesignTimeIdentityContextFactory : IDesignTimeDbContextFactory<IdentityContext> | ||||
| { | ||||
|     public IdentityContext CreateDbContext(string[] args) | ||||
|  |  | |||
|  | @ -51,7 +51,7 @@ app.UseCustomMiddleware(); | |||
| app.MapControllers(); | ||||
| 
 | ||||
| using (var scope = app.Services.CreateScope()) | ||||
| using (var context = scope.ServiceProvider.GetRequiredService<IdentityContext>()) | ||||
| await using (var context = scope.ServiceProvider.GetRequiredService<IdentityContext>()) | ||||
| { | ||||
|     Log.Information("Initializing instance keypair..."); | ||||
|     if (await context.InitializeInstanceAsync()) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue