identity: add proxy controller
This commit is contained in:
		
							parent
							
								
									727f2f6ba2
								
							
						
					
					
						commit
						b95fb76cd4
					
				
					 9 changed files with 446 additions and 10 deletions
				
			
		
							
								
								
									
										329
									
								
								Foxchat.Chat/Migrations/20240521191115_AddLastFetchedAtToUsers.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								Foxchat.Chat/Migrations/20240521191115_AddLastFetchedAtToUsers.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,329 @@ | |||
| // <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("20240521191115_AddLastFetchedAtToUsers")] | ||||
|     partial class AddLastFetchedAtToUsers | ||||
|     { | ||||
|         /// <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<Instant>("LastFetchedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("last_fetched_at"); | ||||
| 
 | ||||
|                     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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,30 @@ | |||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| using NodaTime; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace Foxchat.Chat.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     public partial class AddLastFetchedAtToUsers : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<Instant>( | ||||
|                 name: "last_fetched_at", | ||||
|                 table: "users", | ||||
|                 type: "timestamp with time zone", | ||||
|                 nullable: false, | ||||
|                 defaultValue: NodaTime.Instant.FromUnixTimeTicks(0L)); | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropColumn( | ||||
|                 name: "last_fetched_at", | ||||
|                 table: "users"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -162,6 +162,10 @@ namespace Foxchat.Chat.Migrations | |||
|                         .HasColumnType("uuid") | ||||
|                         .HasColumnName("instance_id"); | ||||
| 
 | ||||
|                     b.Property<Instant>("LastFetchedAt") | ||||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("last_fetched_at"); | ||||
| 
 | ||||
|                     b.Property<string>("RemoteUserId") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ using Microsoft.AspNetCore.Identity; | |||
| using Foxchat.Identity.Database.Models; | ||||
| using Foxchat.Core; | ||||
| using System.Diagnostics; | ||||
| using Foxchat.Identity.Utils; | ||||
| using NodaTime; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| 
 | ||||
|  | @ -24,10 +25,11 @@ public class PasswordAuthController(ILogger logger, IdentityContext db, IClock c | |||
|         var appToken = | ||||
|             HttpContext.GetToken() ?? | ||||
|             throw new UnreachableException(); // GetApplicationOrThrow already gets the token and throws if it's null | ||||
|         var appScopes = appToken.ExpandScopes(); | ||||
| 
 | ||||
|         if (req.Scopes.Except(appToken.Scopes).Any()) | ||||
|         if (req.Scopes.Except(appScopes).Any()) | ||||
|             throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token", | ||||
|                 req.Scopes.Except(appToken.Scopes)); | ||||
|                 req.Scopes.Except(appScopes)); | ||||
| 
 | ||||
|         var acct = new Account | ||||
|         { | ||||
|  | @ -52,10 +54,11 @@ public class PasswordAuthController(ILogger logger, IdentityContext db, IClock c | |||
|     { | ||||
|         var app = HttpContext.GetApplicationOrThrow(); | ||||
|         var appToken = HttpContext.GetToken() ?? throw new UnreachableException(); | ||||
|         var appScopes = appToken.ExpandScopes(); | ||||
| 
 | ||||
|         if (req.Scopes.Except(appToken.Scopes).Any()) | ||||
|         if (req.Scopes.Except(appScopes).Any()) | ||||
|             throw new ApiError.Forbidden("Cannot request token scopes that are not allowed for this token", | ||||
|                 req.Scopes.Except(appToken.Scopes)); | ||||
|                 req.Scopes.Except(appScopes)); | ||||
| 
 | ||||
|         var acct = await db.Accounts.FirstOrDefaultAsync(a => a.Email == req.Email) | ||||
|                    ?? throw new ApiError.NotFound("No user with that email found, or password is incorrect"); | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| using Foxchat.Core; | ||||
| using Foxchat.Identity.Database; | ||||
| using Foxchat.Identity.Database.Models; | ||||
| using Foxchat.Identity.Utils; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using NodaTime; | ||||
| 
 | ||||
|  | @ -14,11 +15,12 @@ public class TokenController(ILogger logger, IdentityContext db, IClock clock) : | |||
|     public async Task<IActionResult> PostToken([FromBody] PostTokenRequest req) | ||||
|     { | ||||
|         var app = await db.GetApplicationAsync(req.ClientId, req.ClientSecret); | ||||
|         var appScopes = app.ExpandScopes(); | ||||
| 
 | ||||
|         var scopes = req.Scope.Split(' '); | ||||
|         if (scopes.Except(app.Scopes).Any()) | ||||
|         if (scopes.Except(appScopes).Any()) | ||||
|         { | ||||
|             throw new ApiError.BadRequest("Invalid or unauthorized scopes"); | ||||
|             throw new ApiError.Forbidden("Invalid or unauthorized scopes", scopes.Except(appScopes)); | ||||
|         } | ||||
| 
 | ||||
|         switch (req.GrantType) | ||||
|  |  | |||
							
								
								
									
										21
									
								
								Foxchat.Identity/Controllers/Proxy/GuildsProxyController.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Foxchat.Identity/Controllers/Proxy/GuildsProxyController.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| using Foxchat.Core.Federation; | ||||
| using Foxchat.Core.Models; | ||||
| using Foxchat.Core.Models.Http; | ||||
| using Foxchat.Identity.Middleware; | ||||
| using Foxchat.Identity.Services; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace Foxchat.Identity.Controllers.Proxy; | ||||
| 
 | ||||
| [Route("/_fox/proxy/guilds")] | ||||
| public class GuildsProxyController( | ||||
|     ILogger logger, | ||||
|     ChatInstanceResolverService chatInstanceResolverService, | ||||
|     RequestSigningService requestSigningService) | ||||
|     : ProxyControllerBase(logger, chatInstanceResolverService, requestSigningService) | ||||
| { | ||||
|     [Authorize("chat_client")] | ||||
|     [HttpPost] | ||||
|     public Task<IActionResult> CreateGuild([FromBody] GuildsApi.CreateGuildRequest req) => | ||||
|         Proxy<Guilds.Guild>(HttpMethod.Post, req); | ||||
| } | ||||
							
								
								
									
										38
									
								
								Foxchat.Identity/Controllers/Proxy/ProxyControllerBase.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Foxchat.Identity/Controllers/Proxy/ProxyControllerBase.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| using Foxchat.Core; | ||||
| using Foxchat.Core.Federation; | ||||
| using Foxchat.Identity.Middleware; | ||||
| using Foxchat.Identity.Services; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace Foxchat.Identity.Controllers.Proxy; | ||||
| 
 | ||||
| [ApiController] | ||||
| [ClientAuthenticate] | ||||
| public class ProxyControllerBase( | ||||
|     ILogger logger, | ||||
|     ChatInstanceResolverService chatInstanceResolverService, | ||||
|     RequestSigningService requestSigningService) : ControllerBase | ||||
| { | ||||
|     internal async Task<IActionResult> Proxy<TResponse>(HttpMethod method, object? body = null) where TResponse : class | ||||
|     { | ||||
|         var acct = HttpContext.GetAccountOrThrow(); | ||||
| 
 | ||||
|         var path = HttpContext.Request.Path.ToString(); | ||||
|         if (!path.StartsWith("/_fox/proxy")) | ||||
|             throw new FoxchatError("Proxy<T> used for endpoint that does not start with /_fox/proxy"); | ||||
|         path = $"/_fox/chat/{path[12..]}"; | ||||
| 
 | ||||
|         if (!HttpContext.Request.Headers.TryGetValue(RequestSigningService.SERVER_HEADER, out var serverHeader)) | ||||
|             throw new ApiError.BadRequest($"Invalid or missing {RequestSigningService.SERVER_HEADER} header."); | ||||
|         var server = serverHeader.ToString(); | ||||
| 
 | ||||
|         logger.Debug("Proxying {Method} request to {Domain}{Path}", method, server, path); | ||||
| 
 | ||||
|         // Identity instances always initiate federation, so we have to make sure the instance knows about us. | ||||
|         // This also serves as a way to make sure the instance being requested actually exists. | ||||
|         await chatInstanceResolverService.ResolveChatInstanceAsync(serverHeader.ToString()); | ||||
| 
 | ||||
|         var resp = await requestSigningService.RequestAsync<TResponse>(method, server, path, acct.Id.ToString(), body); | ||||
|         return Ok(resp); | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| using Foxchat.Core; | ||||
| using Foxchat.Identity.Database; | ||||
| using Foxchat.Identity.Utils; | ||||
| using NodaTime; | ||||
| 
 | ||||
| namespace Foxchat.Identity.Middleware; | ||||
|  | @ -21,10 +22,10 @@ public class ClientAuthorizationMiddleware( | |||
|         } | ||||
| 
 | ||||
|         var token = ctx.GetToken(); | ||||
|         if (token == null || token.Expires > clock.GetCurrentInstant()) | ||||
|         if (token == null || token.Expires < clock.GetCurrentInstant()) | ||||
|             throw new ApiError.Unauthorized("This endpoint requires an authenticated user."); | ||||
|         if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes).Any()) | ||||
|             throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", attribute.Scopes.Except(token.Scopes)); | ||||
|         if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.ExpandScopes()).Any()) | ||||
|             throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", attribute.Scopes.Except(token.ExpandScopes())); | ||||
| 
 | ||||
|         await next(ctx); | ||||
|     } | ||||
|  |  | |||
|  | @ -24,4 +24,12 @@ public static class OauthUtils | |||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     public static string[] ExpandScopes(this Token token) => token.Scopes.Contains("chat_client") | ||||
|         ? Scopes | ||||
|         : token.Scopes; | ||||
| 
 | ||||
|     public static string[] ExpandScopes(this Application app) => app.Scopes.Contains("chat_client") | ||||
|         ? Scopes | ||||
|         : app.Scopes; | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue