diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/DirectoryController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/DirectoryController.cs
new file mode 100644
index 0000000..707a149
--- /dev/null
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/DirectoryController.cs
@@ -0,0 +1,51 @@
+using System.Net.Http.Headers;
+using LibMatrix.Federation;
+using LibMatrix.FederationTest.Services;
+using LibMatrix.Homeservers;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.FederationTest.Controllers.Spec;
+
+[ApiController]
+[Route("_matrix/federation/")]
+public class DirectoryController(ServerAuthService serverAuth) : ControllerBase {
+ [HttpGet("v1/publicRooms")]
+ [HttpPost("v1/publicRooms")]
+ public async Task<IActionResult> GetPublicRooms() {
+ if (Request.Headers.ContainsKey("Authorization")) {
+ Console.WriteLine("INFO | Authorization header found.");
+ await serverAuth.AssertValidAuthentication();
+ }
+ else Console.WriteLine("INFO | Room directory request without auth");
+
+ var rooms = new List<PublicRoomDirectoryResult.PublicRoomListItem> {
+ new() {
+ GuestCanJoin = false,
+ RoomId = "!tuiLEoMqNOQezxILzt:rory.gay",
+ NumJoinedMembers = Random.Shared.Next(),
+ WorldReadable = false,
+ CanonicalAlias = "#libmatrix:rory.gay",
+ Name = "Rory&::LibMatrix",
+ Topic = $"A .NET {Environment.Version.Major} library for interacting with Matrix"
+ }
+ };
+ return Ok(new PublicRoomDirectoryResult() {
+ Chunk = rooms,
+ TotalRoomCountEstimate = rooms.Count
+ });
+ }
+
+ [HttpGet("v1/query/profile")]
+ public async Task<IActionResult> GetProfile([FromQuery(Name = "user_id")] string userId) {
+ if (Request.Headers.ContainsKey("Authorization")) {
+ Console.WriteLine("INFO | Authorization header found.");
+ await serverAuth.AssertValidAuthentication();
+ }
+ else Console.WriteLine("INFO | Profile request without auth");
+
+ return Ok(new {
+ avatar_url = "mxc://rory.gay/ocRVanZoUTCcifcVNwXgbtTg",
+ displayname = "Rory&::LibMatrix.FederationTest"
+ });
+ }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs
new file mode 100644
index 0000000..d96bef5
--- /dev/null
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs
@@ -0,0 +1,43 @@
+using LibMatrix.Abstractions;
+using LibMatrix.Federation.Extensions;
+using LibMatrix.FederationTest.Services;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses.Federation;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.FederationTest.Controllers.Spec;
+
+[ApiController]
+[Route("_matrix/key/v2/")]
+public class FederationKeysController(FederationTestConfiguration config, FederationKeyStore keyStore) {
+ static FederationKeysController() {
+ Console.WriteLine("INFO | FederationKeysController initialized.");
+ }
+
+ private static SignedObject<ServerKeysResponse>? _cachedServerKeysResponse;
+ private static SemaphoreSlim _serverKeyCacheLock = new SemaphoreSlim(1, 1);
+
+ [HttpGet("server")]
+ public async Task<SignedObject<ServerKeysResponse>> GetServerKeys() {
+ await _serverKeyCacheLock.WaitAsync();
+ if (_cachedServerKeysResponse == null || _cachedServerKeysResponse.TypedContent.ValidUntil < DateTime.Now + TimeSpan.FromSeconds(30)) {
+ var keys = keyStore.GetCurrentSigningKey();
+ _cachedServerKeysResponse = new ServerKeysResponse() {
+ ValidUntil = DateTime.Now + TimeSpan.FromMinutes(5),
+ ServerName = config.ServerName,
+ OldVerifyKeys = [],
+ VerifyKeysById = new() {
+ {
+ keys.CurrentSigningKey.KeyId, new ServerKeysResponse.CurrentVerifyKey() {
+ Key = keys.CurrentSigningKey.PublicKey //.ToUnpaddedBase64(),
+ }
+ }
+ }
+ }.Sign(keys.CurrentSigningKey);
+ }
+
+ _serverKeyCacheLock.Release();
+
+ return _cachedServerKeysResponse;
+ }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs
new file mode 100644
index 0000000..d146cfd
--- /dev/null
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs
@@ -0,0 +1,19 @@
+using LibMatrix.Homeservers;
+using LibMatrix.Responses.Federation;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.FederationTest.Controllers.Spec;
+
+[ApiController]
+[Route("_matrix/federation/v1/")]
+public class FederationVersionController : ControllerBase {
+ [HttpGet("version")]
+ public ServerVersionResponse GetVersion() {
+ return new ServerVersionResponse {
+ Server = new() {
+ Name = "LibMatrix.Federation",
+ Version = "0.0.0",
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/MembershipsController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/MembershipsController.cs
new file mode 100644
index 0000000..7c561ad
--- /dev/null
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/MembershipsController.cs
@@ -0,0 +1,41 @@
+using System.Net.Http.Headers;
+using LibMatrix.Federation;
+using LibMatrix.Federation.FederationTypes;
+using LibMatrix.FederationTest.Services;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.FederationTest.Controllers.Spec;
+
+[ApiController]
+[Route("_matrix/federation/")]
+public class MembershipsController(ServerAuthService sas) : ControllerBase {
+ [HttpGet("v1/make_join/{roomId}/{userId}")]
+ [HttpPut("v1/send_join/{roomId}/{eventId}")]
+ [HttpPut("v2/send_join/{roomId}/{eventId}")]
+ [HttpGet("v1/make_knock/{roomId}/{userId}")]
+ [HttpPut("v1/send_knock/{roomId}/{eventId}")]
+ [HttpGet("v1/make_leave/{roomId}/{eventId}")]
+ [HttpPut("v1/send_leave/{roomId}/{eventId}")]
+ [HttpPut("v2/send_leave/{roomId}/{eventId}")]
+ public async Task<IActionResult> JoinKnockMemberships() {
+ await sas.AssertValidAuthentication();
+ return NotFound(new MatrixException() {
+ ErrorCode = MatrixException.ErrorCodes.M_NOT_FOUND,
+ Error = "Rory&::LibMatrix.FederationTest does not support membership events."
+ }.GetAsObject());
+ }
+
+ // [HttpPut("v1/invite/{roomId}/{eventId}")]
+ [HttpPut("v2/invite/{roomId}/{eventId}")]
+ public async Task<IActionResult> InviteHandler([FromBody] RoomInvite invite) {
+ await sas.AssertValidAuthentication();
+
+ Console.WriteLine($"Received invite event from {invite.Event.Sender} for room {invite.Event.RoomId} (version {invite.RoomVersion})\n" +
+ $"{invite.InviteRoomState.Count} invite room state events.");
+
+ return NotFound(new MatrixException() {
+ ErrorCode = MatrixException.ErrorCodes.M_NOT_FOUND,
+ Error = "Rory&::LibMatrix.FederationTest does not support membership events."
+ }.GetAsObject());
+ }
+}
\ No newline at end of file
diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs
new file mode 100644
index 0000000..b91868c
--- /dev/null
+++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs
@@ -0,0 +1,19 @@
+using LibMatrix.Services.WellKnownResolver.WellKnownResolvers;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LibMatrix.FederationTest.Controllers.Spec;
+
+[ApiController]
+[Route(".well-known/")]
+public class WellKnownController(ILogger<WellKnownController> logger) : ControllerBase {
+ static WellKnownController() {
+ Console.WriteLine("INFO | WellKnownController initialized.");
+ }
+ [HttpGet("matrix/server")]
+ public ServerWellKnown GetMatrixServerWellKnown() {
+ // {Request.Headers["X-Forwarded-Proto"].FirstOrDefault(Request.Scheme)}://
+ return new() {
+ Homeserver = $"{Request.Headers["X-Forwarded-Host"].FirstOrDefault(Request.Host.Host)}:{Request.Headers["X-Forwarded-Port"].FirstOrDefault("443")}",
+ };
+ }
+}
\ No newline at end of file
|