summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ModAS.Server/AppServiceRegistration.cs4
-rw-r--r--ModAS.Server/Authentication/AuthMiddleware.cs48
-rw-r--r--ModAS.Server/Controllers/Admin/RoomQueryController.cs19
-rw-r--r--ModAS.Server/Controllers/AppService/PingController.cs74
-rw-r--r--ModAS.Server/Controllers/AppService/TransactionsController.cs1
-rw-r--r--ModAS.Server/Controllers/HomeController.cs38
-rw-r--r--ModAS.Server/Program.cs21
-rw-r--r--ModAS.Server/Services/AuthenticationService.cs76
-rw-r--r--ModAS.Server/Services/PingTask.cs9
-rw-r--r--ModAS.Server/Services/UserProviderService.cs1
-rw-r--r--ModAS.Server/Version.cs7
-rw-r--r--ModAS.Server/appsettings.Development.json5
-rw-r--r--ModAS.Server/wwwroot/index.html1
-rwxr-xr-xcommit11
14 files changed, 112 insertions, 203 deletions
diff --git a/ModAS.Server/AppServiceRegistration.cs b/ModAS.Server/AppServiceRegistration.cs
index 63dabba..b6d28e6 100644
--- a/ModAS.Server/AppServiceRegistration.cs
+++ b/ModAS.Server/AppServiceRegistration.cs
@@ -6,7 +6,7 @@ using System.Text.Json.Serialization;
 namespace ModAS.Server;
 
 public class AppServiceRegistration {
-    private const string ValidChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~+/";
+    private const string ValidChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
     private const string ExtendedValidChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~+/";
 
     [JsonPropertyName("as_token")]
@@ -16,7 +16,7 @@ public class AppServiceRegistration {
     public string HomeserverToken { get; set; } = RandomNumberGenerator.GetString(ExtendedValidChars, RandomNumberGenerator.GetInt32(512, 1024));
 
     [JsonPropertyName("id")]
-    public string Id { get; set; } = "ModAS-"+RandomNumberGenerator.GetString(ValidChars, 5);
+    public string Id { get; set; } = "ModAS-" + RandomNumberGenerator.GetString(ValidChars, 5);
 
     [JsonPropertyName("namespaces")]
     public NamespacesObject Namespaces { get; set; } = new() {
diff --git a/ModAS.Server/Authentication/AuthMiddleware.cs b/ModAS.Server/Authentication/AuthMiddleware.cs
index 8b7266f..11d4d39 100644
--- a/ModAS.Server/Authentication/AuthMiddleware.cs
+++ b/ModAS.Server/Authentication/AuthMiddleware.cs
@@ -1,3 +1,6 @@
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Specialized;
 using System.Net.Http.Headers;
 using System.Text.Json;
 using LibMatrix;
@@ -23,14 +26,16 @@ public class AuthMiddleware(RequestDelegate next, ILogger<AuthMiddleware> logger
         }
 
         var authAttribute = endpoint?.Metadata.GetMetadata<UserAuthAttribute>();
-        if (authAttribute is not null)
-            logger.LogInformation($"{nameof(Route)} authorization: {authAttribute.ToJson()}");
-        else if (string.IsNullOrWhiteSpace(accessToken)) {
-            // auth is optional if auth attribute isnt set
-            Console.WriteLine($"Allowing unauthenticated request, AuthAttribute is not set!");
-            await next(context);
-            return;
+        if (authAttribute is null) {
+            if (string.IsNullOrWhiteSpace(accessToken)) {
+                // auth is optional if auth attribute isnt set
+                Console.WriteLine($"Allowing unauthenticated request, AuthAttribute is not set!");
+                await next(context);
+                return;
+            }
         }
+        else
+            logger.LogInformation($"{nameof(Route)} authorization: {authAttribute.ToJson()}");
 
         if (string.IsNullOrWhiteSpace(accessToken))
             if (authAttribute is not null) {
@@ -42,20 +47,27 @@ public class AuthMiddleware(RequestDelegate next, ILogger<AuthMiddleware> logger
                 return;
             }
 
+        if (await ValidateAuth(authAttribute, context, accessToken))
+            await next(context);
+    }
+
+    private async Task<bool> ValidateAuth(UserAuthAttribute? authAttribute, HttpContext context, string? accessToken) {
         try {
-            switch (authAttribute.AuthType) {
+            switch (authAttribute?.AuthType) {
+                case null:
                 case AuthType.User:
-                    var authUser = await GetAuthUser(accessToken);
+                    if (string.IsNullOrWhiteSpace(accessToken) && authAttribute is null)
+                        return true; //we dont care in this case
+                    var authUser = await GetAuthUser(accessToken!);
                     context.Items.Add("AuthUser", authUser);
-                    break;
+                    return true;
                 case AuthType.Server:
                     if (asr.HomeserverToken != accessToken)
                         throw new MatrixException() {
                             ErrorCode = "M_UNAUTHORIZED",
                             Error = "Invalid access token"
                         };
-
-                    break;
+                    return true;
                 default:
                     throw new ArgumentOutOfRangeException();
             }
@@ -63,17 +75,17 @@ public class AuthMiddleware(RequestDelegate next, ILogger<AuthMiddleware> logger
         catch (MatrixException e) {
             context.Response.StatusCode = 401;
             await context.Response.WriteAsJsonAsync(e.GetAsObject());
-            return;
+            return false;
         }
-
-        await next(context);
     }
 
+    private static readonly Dictionary<string, AuthUser> AuthCache = new();
+
     private async Task<AuthUser> GetAuthUser(string accessToken) {
-        AuthenticatedHomeserverGeneric? homeserver;
-        homeserver = await hsProvider.GetAuthenticatedWithToken(config.ServerName, accessToken, config.HomeserverUrl);
+        if (AuthCache.TryGetValue(accessToken, out var authUser)) return authUser;
+        var homeserver = await hsProvider.GetAuthenticatedWithToken(config.ServerName, accessToken, config.HomeserverUrl);
 
-        return new AuthUser() {
+        return AuthCache[accessToken] = new AuthUser() {
             Homeserver = homeserver,
             AccessToken = accessToken,
             Roles = config.Roles.Where(r => r.Value.Contains(homeserver.WhoAmI.UserId)).Select(r => r.Key).ToList()
diff --git a/ModAS.Server/Controllers/Admin/RoomQueryController.cs b/ModAS.Server/Controllers/Admin/RoomQueryController.cs
index a49e5c0..02100e9 100644
--- a/ModAS.Server/Controllers/Admin/RoomQueryController.cs
+++ b/ModAS.Server/Controllers/Admin/RoomQueryController.cs
@@ -25,16 +25,12 @@ public class RoomQueryController(UserProviderService ahsProvider, ModASConfigura
         getUsersSpan.End();
 
         var collectRoomsSpan = currentTransaction.StartSpan("collectRooms", ApiConstants.TypeApp);
-        // var userRoomLists = validUsers.Values.Select(ahs => ahs.GetJoinedRooms()).ToList();
-        // await Task.WhenAll(userRoomLists);
-        // var rooms = userRoomLists.SelectMany(r => r.Result).DistinctBy(x => x.RoomId).ToFrozenSet();
         var roomsTasks = validUsers.Values.Select(u => userProviderService.GetUserRoomsCached(u.WhoAmI.UserId)).ToList();
         await Task.WhenAll(roomsTasks);
         var rooms = roomsTasks.SelectMany(r => r.Result).DistinctBy(x => x.RoomId).ToFrozenSet();
         collectRoomsSpan.End();
 
         var collectRoomStateSpan = currentTransaction.StartSpan("collectRoomState", ApiConstants.TypeApp);
-        //make sure we lock!!!!
         await stateCacheService.EnsureCachedFromRoomList(rooms);
         var roomStateTasks = rooms.Select(GetRoomState).ToAsyncEnumerable();
         var awaitRoomStateSpan = currentTransaction.StartSpan("fetchRoomState", ApiConstants.TypeApp);
@@ -46,6 +42,7 @@ public class RoomQueryController(UserProviderService ahsProvider, ModASConfigura
             var roomMembers = roomState.Where(r => r.Type == RoomMemberEventContent.EventId).ToFrozenSet();
             var localRoomMembers = roomMembers.Where(x => x.StateKey.EndsWith(':' + config.ServerName)).ToFrozenSet();
             var nonMemberState = roomState.Where(x => !roomMembers.Contains(x)).ToFrozenSet();
+            var creationEvent = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId);
             filterStateEventsSpan.End();
             var buildResultSpan = currentTransaction.StartSpan($"buildResult {room.RoomId}", ApiConstants.TypeApp);
 
@@ -56,21 +53,21 @@ public class RoomQueryController(UserProviderService ahsProvider, ModASConfigura
                 //members
                 TotalMembers = roomMembers.Count,
                 TotalLocalMembers = localRoomMembers.Count,
-                JoinedMembers = roomMembers.Count(x => (x.TypedContent as RoomMemberEventContent)?.Membership == "join"),
-                JoinedLocalMembers = localRoomMembers.Count(x => (x.TypedContent as RoomMemberEventContent)?.Membership == "join"),
+                JoinedMembers = roomMembers.Count(x => x.RawContent?["membership"]?.GetValue<string>() == "join"),
+                JoinedLocalMembers = localRoomMembers.Count(x => x.RawContent?["membership"]?.GetValue<string>() == "join"),
                 //-members
                 //creation event
-                Creator = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.Sender,
-                Version = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["room_version"]?.GetValue<string>(),
-                Type = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["type"]?.GetValue<string>(),
-                Federatable = nonMemberState.FirstOrDefault(r => r.Type == RoomCreateEventContent.EventId)?.RawContent?["m.federate"]?.GetValue<bool>() ?? true,
+                Creator = creationEvent!.Sender,
+                Version = creationEvent.RawContent?["room_version"]?.GetValue<string>(),
+                Type = creationEvent.RawContent?["type"]?.GetValue<string>(),
+                Federatable = creationEvent.RawContent?["m.federate"]?.GetValue<bool>() ?? true,
                 //-creation event
                 Name = nonMemberState.FirstOrDefault(r => r.Type == RoomNameEventContent.EventId)?.RawContent?["name"]?.GetValue<string>(),
                 CanonicalAlias = nonMemberState.FirstOrDefault(r => r.Type == RoomCanonicalAliasEventContent.EventId)?.RawContent?["alias"]?.GetValue<string>(),
-                JoinRules = nonMemberState.FirstOrDefault(r => r.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.GetValue<string>(),
                 GuestAccess = nonMemberState.FirstOrDefault(r => r.Type == RoomGuestAccessEventContent.EventId)?.RawContent?["guest_access"]?.GetValue<string>(),
                 HistoryVisibility =
                     nonMemberState.FirstOrDefault(r => r.Type == RoomHistoryVisibilityEventContent.EventId)?.RawContent?["history_visibility"]?.GetValue<string>(),
+                JoinRules = nonMemberState.FirstOrDefault(r => r.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.GetValue<string>(),
                 Public = nonMemberState.FirstOrDefault(r => r.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.GetValue<string>() == "public",
                 Encryption = nonMemberState.FirstOrDefault(r => r.Type == RoomEncryptionEventContent.EventId)?.RawContent?["algorithm"]?.GetValue<string>(),
                 AvatarUrl = nonMemberState.FirstOrDefault(r => r.Type == RoomAvatarEventContent.EventId)?.RawContent?["url"]?.GetValue<string>(),
diff --git a/ModAS.Server/Controllers/AppService/PingController.cs b/ModAS.Server/Controllers/AppService/PingController.cs
index 7b073c1..6db9033 100644
--- a/ModAS.Server/Controllers/AppService/PingController.cs
+++ b/ModAS.Server/Controllers/AppService/PingController.cs
@@ -1,73 +1,21 @@
-using System.IO.Pipelines;
-using System.Net;
-using System.Net.Http.Headers;
-using System.Text.Json;
-using ArcaneLibs;
-using LibMatrix;
-using LibMatrix.EventTypes.Spec;
+using System.Text.Json.Serialization;
 using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration.Json;
-using ModAS.Server;
+using Microsoft.OpenApi.Validations.Rules;
 using ModAS.Server.Attributes;
-using ModAS.Server.Services;
-using MxApiExtensions.Services;
 
 namespace ModAS.Server.Controllers.AppService;
 
 [ApiController]
-public class PingController(
-    AppServiceRegistration asr,
-    ModASConfiguration config,
-    UserProviderService userProvider,
-    RoomContextService roomContextService,
-    RoomStateCacheService stateCacheService) : ControllerBase {
-    private static List<string> _ignoredInvalidationEvents { get; set; } = [
-        RoomMessageEventContent.EventId,
-        RoomMessageReactionEventContent.EventId
-    ];
-
-    [HttpPut("/_matrix/app/v1/transactions/{txnId}")]
+[ApiExplorerSettings(IgnoreApi = true)] //hide from swagger
+public class PingController : ControllerBase {
+    [HttpPost("/_matrix/app/v1/ping")]
     [UserAuth(AuthType = AuthType.Server)]
-    public async Task<IActionResult> PutTransactions(string txnId) {
-        var data = await JsonSerializer.DeserializeAsync<EventList>(Request.Body);
-        Console.WriteLine(
-            $"PutTransaction: {txnId}: {data.Events.Count} events, {Util.BytesToString(Request.Headers.ContentLength ?? Request.ContentLength ?? Request.Body.Length)}");
-
-        if (!Directory.Exists("data"))
-            Directory.CreateDirectory("data");
-        Directory.CreateDirectory($"data/{txnId}");
-        // var pipe = PipeReader.Create(Request.Body);
-        // await using var file = System.IO.File.OpenWrite($"data/{txnId}");
-        // await pipe.CopyToAsync(file);
-        // await pipe.CompleteAsync();
-        //
-        // Console.WriteLine($"PutTransaction: {txnId}: {Util.BytesToString(file.Length)}");
-        for (var i = 0; i < data.Events.Count; i++) {
-            var evt = data.Events[i];
-            Console.WriteLine($"PutTransaction: {txnId}/{i}: {evt.Type} {evt.StateKey} {evt.Sender}");
-            await System.IO.File.WriteAllTextAsync($"data/{txnId}/{i}-{evt.Type.Replace("/", "")}-{evt.StateKey.Replace("/", "")}-{evt.Sender?.Replace("/", "")}.json",
-                JsonSerializer.Serialize(evt));
-
-            if (evt.Sender.EndsWith(':' + config.ServerName)) {
-                Console.WriteLine("PutTransaction: sender is local user, updating data...");
-                try {
-                    var user = await userProvider.GetImpersonatedHomeserver(evt.Sender);
-                    var rooms = await user.GetJoinedRooms();
-                    foreach (var room in rooms) {
-                        await roomContextService.GetRoomContext(room);
-                    }
-                }
-                catch (Exception e) {
-                    Console.WriteLine($"PutTransaction: failed to update data: {e}");
-                }
-            }
-            else
-                Console.WriteLine("PutTransaction: sender is remote user");
-
-            if (!string.IsNullOrWhiteSpace(evt.RoomId) && !_ignoredInvalidationEvents.Contains(evt.Type))
-                await stateCacheService.InvalidateRoomState(evt.RoomId);
-        }
-
+    public IActionResult PutTransactions([FromBody] TransactionIdContainer data) {
         return Ok(new { });
     }
+
+    public class TransactionIdContainer {
+        [JsonPropertyName("transaction_id")]
+        public string TransactionId { get; set; }
+    }
 }
\ No newline at end of file
diff --git a/ModAS.Server/Controllers/AppService/TransactionsController.cs b/ModAS.Server/Controllers/AppService/TransactionsController.cs
index 53bfaf5..fd8a6d3 100644
--- a/ModAS.Server/Controllers/AppService/TransactionsController.cs
+++ b/ModAS.Server/Controllers/AppService/TransactionsController.cs
@@ -15,6 +15,7 @@ using MxApiExtensions.Services;
 namespace ModAS.Server.Controllers.AppService;
 
 [ApiController]
+[ApiExplorerSettings(IgnoreApi = true)] //hide from swagger
 public class TransactionsController(
     AppServiceRegistration asr,
     ModASConfiguration config,
diff --git a/ModAS.Server/Controllers/HomeController.cs b/ModAS.Server/Controllers/HomeController.cs
index eb17966..48432d5 100644
--- a/ModAS.Server/Controllers/HomeController.cs
+++ b/ModAS.Server/Controllers/HomeController.cs
@@ -1,5 +1,13 @@
 using System.Diagnostics;
+using System.Net.Http.Headers;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Web;
 using Microsoft.AspNetCore.Mvc;
+using ModAS.Server.Controllers.AppService;
+using MxApiExtensions.Services;
 
 namespace ModAS.Server.Controllers;
 
@@ -7,10 +15,38 @@ namespace ModAS.Server.Controllers;
 ///    Manages the visual homepage.
 /// </summary>
 [ApiController]
-public class HomeController : Controller {
+public class HomeController(AppServiceRegistration asr, ModASConfiguration config) : Controller {
+    private const string ValidChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
     /// <inheritdoc cref="HomeController"/>
     [HttpGet("/_matrix/_modas")]
+    [ApiExplorerSettings(IgnoreApi = true)] //hide from swagger
     public IActionResult Index() {
         return LocalRedirect("/index.html");
     }
+
+    [HttpGet("/_matrix/_modas/version")]
+    public IActionResult Version() {
+        return Ok(new {
+            Version = Modas.Server.Version.VersionString
+        });
+    }
+
+    [HttpGet("/_matrix/_modas/ping")]
+    public async Task<IActionResult> Ping() {
+        var txn = new PingController.TransactionIdContainer() {
+            TransactionId = RandomNumberGenerator.GetString(ValidChars, 32)
+        };
+        var url = $"{config.HomeserverUrl}/_matrix/client/v1/appservice/{HttpUtility.UrlEncode(asr.Id)}/ping";
+        var hrm = new HttpRequestMessage(HttpMethod.Post, url) {
+            Content = new StringContent(JsonSerializer.Serialize(txn), Encoding.UTF8, "application/json"),
+            Headers = {
+                Authorization = new AuthenticationHeaderValue("Bearer", asr.AppServiceToken)
+            }
+        };
+        var req = await new HttpClient().SendAsync(hrm);
+        var resp = await req.Content.ReadFromJsonAsync<JsonObject>();
+        resp!["tnxId"] = txn.TransactionId;
+        return Ok(resp);
+    }
 }
\ No newline at end of file
diff --git a/ModAS.Server/Program.cs b/ModAS.Server/Program.cs
index 248243f..125dc93 100644
--- a/ModAS.Server/Program.cs
+++ b/ModAS.Server/Program.cs
@@ -5,7 +5,6 @@ using System.Diagnostics;
 using System.Text.Json;
 using Elastic.Apm;
 using Elastic.Apm.Api;
-using Elastic.Apm.AspNetCore;
 using Elastic.Apm.NetCoreAll;
 using LibMatrix;
 using LibMatrix.Services;
@@ -13,14 +12,10 @@ using ModAS.Server.Authentication;
 using ModAS.Server.Services;
 using MxApiExtensions.Services;
 
-// var builder = WebApplication.CreateBuilder(args);
 var builder = WebApplication.CreateSlimBuilder(args);
 
 builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.WriteIndented = true; });
-///add wwwroot
-// builder.Services.AddDirectoryBrowser();
-// Add services to the container.
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+
 builder.Services.AddEndpointsApiExplorer();
 builder.Services.AddSwaggerGen(c => {
     c.SwaggerDoc("v1", new OpenApiInfo() {
@@ -35,19 +30,15 @@ builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 
 builder.Services.AddSingleton<ModASConfiguration>();
 
-builder.Services.AddSingleton<AuthenticationService>();
 builder.Services.AddSingleton<UserProviderService>();
 builder.Services.AddSingleton<RoomContextService>();
 builder.Services.AddSingleton<RoomStateCacheService>();
 // builder.Services.AddScoped<UserContextService>();
 
-builder.Services.AddSingleton<TieredStorageService>(x => {
-    var config = x.GetRequiredService<ModASConfiguration>();
-    return new TieredStorageService(
-        cacheStorageProvider: new FileStorageProvider("/run"),
-        dataStorageProvider: new FileStorageProvider("/run")
-    );
-});
+builder.Services.AddSingleton<TieredStorageService>(x => new TieredStorageService(
+    cacheStorageProvider: new FileStorageProvider("/run"),
+    dataStorageProvider: new FileStorageProvider("/run")
+));
 builder.Services.AddRoryLibMatrixServices();
 
 //trace init time for app service registration
@@ -117,8 +108,6 @@ Agent.AddFilter((ISpan span) => {
 
 app.UseFileServer();
 
-app.UseRouting();
-
 app.UseCors("Open");
 
 app.MapControllers();
diff --git a/ModAS.Server/Services/AuthenticationService.cs b/ModAS.Server/Services/AuthenticationService.cs
deleted file mode 100644
index 8efc08c..0000000
--- a/ModAS.Server/Services/AuthenticationService.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.Net.Http.Headers;
-using LibMatrix;
-using LibMatrix.Services;
-using MxApiExtensions.Extensions;
-using MxApiExtensions.Services;
-
-namespace ModAS.Server.Services;
-
-public class AuthenticationService(
-    ILogger<AuthenticationService> logger,
-    ModASConfiguration config,
-    IHttpContextAccessor request,
-    HomeserverProviderService homeserverProviderService) {
-    private readonly HttpRequest _request = request.HttpContext!.Request;
-
-    private static Dictionary<string, string> _tokenMap = new();
-
-    internal string? GetToken(bool fail = true) {
-        //_request.GetTypedHeaders().Get<AuthenticationHeaderValue>("Authorization")?.Parameter != asr.HomeserverToken
-
-        string? token = null;
-        if (_request.GetTypedHeaders().TryGet<AuthenticationHeaderValue>("Authorization", out var authHeader) && !string.IsNullOrWhiteSpace(authHeader?.Parameter)) {
-            token = authHeader.Parameter;
-        }
-        else if (_request.Query.ContainsKey("access_token")) {
-            token = _request.Query["access_token"];
-        }
-
-        if (string.IsNullOrWhiteSpace(token) && fail) {
-            throw new MatrixException() {
-                ErrorCode = "M_MISSING_TOKEN",
-                Error = "Missing access token"
-            };
-        }
-
-        return token;
-    }
-
-    public async Task<string> GetMxidFromToken(string? token = null, bool fail = true) {
-        token ??= GetToken(fail);
-        if (string.IsNullOrWhiteSpace(token)) {
-            if (fail) {
-                throw new MatrixException() {
-                    ErrorCode = "M_MISSING_TOKEN",
-                    Error = "Missing access token"
-                };
-            }
-
-            return "@anonymous:*";
-        }
-
-        if (_tokenMap is not { Count: > 0 } && File.Exists("token_map")) {
-            _tokenMap = (await File.ReadAllLinesAsync("token_map"))
-                .Select(l => l.Split('\t'))
-                .ToDictionary(l => l[0], l => l[1]);
-        }
-
-        if (_tokenMap.TryGetValue(token, out var mxid)) return mxid;
-
-        logger.LogInformation("Looking up mxid for token {}", token);
-        var hs = await homeserverProviderService.GetAuthenticatedWithToken(config.ServerName, token, config.HomeserverUrl);
-        try {
-            var res = hs.WhoAmI.UserId;
-            logger.LogInformation("Got mxid {} for token {}", res, token);
-
-            return res;
-        }
-        catch (MatrixException e) {
-            if (e.ErrorCode == "M_UNKNOWN_TOKEN") {
-                return null;
-            }
-
-            throw;
-        }
-    }
-}
\ No newline at end of file
diff --git a/ModAS.Server/Services/PingTask.cs b/ModAS.Server/Services/PingTask.cs
deleted file mode 100644
index 99a8f40..0000000
--- a/ModAS.Server/Services/PingTask.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace ModAS.Server.Services;
-
-public class PingTask : IHostedService, IDisposable {
-    public Task StartAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
-
-    public Task StopAsync(CancellationToken cancellationToken) => throw new NotImplementedException();
-
-    public void Dispose() => throw new NotImplementedException();
-}
\ No newline at end of file
diff --git a/ModAS.Server/Services/UserProviderService.cs b/ModAS.Server/Services/UserProviderService.cs
index bb281c4..7e4065c 100644
--- a/ModAS.Server/Services/UserProviderService.cs
+++ b/ModAS.Server/Services/UserProviderService.cs
@@ -11,7 +11,6 @@ using MxApiExtensions.Services;
 namespace ModAS.Server.Services;
 
 public class UserProviderService(
-    AuthenticationService authenticationService,
     HomeserverProviderService homeserverProviderService,
     IHttpContextAccessor request,
     ModASConfiguration config,
diff --git a/ModAS.Server/Version.cs b/ModAS.Server/Version.cs
index 19aa81c..2f26dd7 100644
--- a/ModAS.Server/Version.cs
+++ b/ModAS.Server/Version.cs
@@ -1,5 +1,10 @@
 namespace Modas.Server;
+#pragma warning disable CS1591
 public static class Version
 {
-    public const string Text = "master@v0+";
+    public const string VersionString = "v0-master(+1) (c5b72e6)";
+    public const string Branch = "master";
+    public const string Tag = "v0";
+    public const string Commit = "c5b72e6f002a637d542068be88d70936150c8818";
 }
+#pragma warning restore CS1591
diff --git a/ModAS.Server/appsettings.Development.json b/ModAS.Server/appsettings.Development.json
index f9b6b0f..f9b9fa2 100644
--- a/ModAS.Server/appsettings.Development.json
+++ b/ModAS.Server/appsettings.Development.json
@@ -7,6 +7,9 @@
   },
   "ModAS": {
     "ServerName": "rory.gay",
-    "HomeserverUrl": "https://matrix.rory.gay"
+    "HomeserverUrl": "https://matrix.rory.gay",
+    "Roles":{
+      "Admin": []
+    }
   }
 }
diff --git a/ModAS.Server/wwwroot/index.html b/ModAS.Server/wwwroot/index.html
index 391570a..d7eb73a 100644
--- a/ModAS.Server/wwwroot/index.html
+++ b/ModAS.Server/wwwroot/index.html
@@ -16,6 +16,7 @@
         <h1 style="margin-bottom: 0;">ModAS</h1>
         <p style="color: #ffffff66; font-size: 0.75em;"><i>Moderation at your fingertips</i></p>
         <p>Server is running.</p>
+        <a href="/swagger">API documentation</a>
     </div>
 </body>
 </html>
\ No newline at end of file
diff --git a/commit b/commit
index 69f5cc8..083d9cc 100755
--- a/commit
+++ b/commit
@@ -2,10 +2,13 @@
 
 cat <<EOF > ModAS.Server/Version.cs
 namespace Modas.Server;
+#pragma warning disable CS1591
 public static class Version
 {
-    public const string Text = "$(git rev-parse --abbrev-ref HEAD)@$(git describe --tags --abbrev=0)+`git log $(git describe --tags --abbrev=0)..HEAD --oneline`";
+    public const string VersionString = "$(git describe --tags --abbrev=0)-$(git rev-parse --abbrev-ref HEAD)(+$(git log $(git describe --tags --abbrev=0)..HEAD --oneline | wc -l)) ($(git rev-parse --short HEAD))";
+    public const string Branch = "$(git rev-parse --abbrev-ref HEAD)";
+    public const string Tag = "$(git describe --tags --abbrev=0)";
+    public const string Commit = "$(git rev-parse HEAD)";
 }
-EOF
-
-#git log $(git describe --tags --abbrev=0)..HEAD --oneline
\ No newline at end of file
+#pragma warning restore CS1591
+EOF
\ No newline at end of file