summary refs log tree commit diff
path: root/ModAS.Server/Services/UserProviderService.cs
blob: 7e4065c952506624b42b3f22092da56d08261deb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using System.Collections.Concurrent;
using ArcaneLibs.Extensions;
using Elastic.Apm;
using Elastic.Apm.Api;
using LibMatrix;
using LibMatrix.Homeservers;
using LibMatrix.RoomTypes;
using LibMatrix.Services;
using MxApiExtensions.Services;

namespace ModAS.Server.Services;

public class UserProviderService(
    HomeserverProviderService homeserverProviderService,
    IHttpContextAccessor request,
    ModASConfiguration config,
    AppServiceRegistration asRegistration
) {
    public HttpContext? _context = request.HttpContext;

    private static ITransaction currentTransaction => Agent.Tracer.CurrentTransaction;

    private SemaphoreSlim updateLock = new(1,1);
    
    public ConcurrentDictionary<string, AuthenticatedHomeserverGeneric> KnownUsers { get; set; } = new();
    public ConcurrentDictionary<string, DateTime> UserValidationExpiry { get; set; } = new();
    public ConcurrentDictionary<string, List<string>> CachedUserRooms { get; set; } = new();
    public ConcurrentDictionary<string, DateTime> CachedUserRoomsExpiry { get; set; } = new();
    

    public async Task<AuthenticatedHomeserverGeneric> GetImpersonatedHomeserver(string mxid) {
        var span = currentTransaction.StartSpan("GetImpersonatedHomeserver", ApiConstants.TypeApp);
        if (!KnownUsers.TryGetValue(mxid, out var homeserver)) {
            var getUserSpan = currentTransaction.StartSpan($"GetUser - {mxid}", ApiConstants.TypeApp);
            homeserver = await homeserverProviderService.GetAuthenticatedWithToken(config.ServerName, asRegistration.AppServiceToken, config.HomeserverUrl, mxid);
            KnownUsers.TryAdd(mxid, homeserver);
            getUserSpan.End();
        }

        await homeserver.SetImpersonate(mxid);
        span.End();
        return homeserver;
    }

    public async Task<Dictionary<string, AuthenticatedHomeserverGeneric>> GetValidUsers() {
        var span = currentTransaction.StartSpan("GetValidUsers", ApiConstants.TypeApp);
        var tasks = KnownUsers.Select(kvp => ValidateUser(kvp.Key, kvp.Value)).ToList();
        var results = await Task.WhenAll(tasks);
        var validUsers = results.Where(r => r.Value is not null).ToDictionary(r => r.Key, r => r.Value!);
        span.End();
        return validUsers;
    }
    
    public async Task<List<GenericRoom>> GetUserRoomsCached(string mxid) {
        var span = currentTransaction.StartSpan($"GetUserRoomsCached - {mxid}", ApiConstants.TypeApp);
        var hs = await GetImpersonatedHomeserver(mxid);
        if (CachedUserRoomsExpiry.TryGetValue(mxid, out var expiry) && expiry > DateTime.Now) {
            if (CachedUserRooms.TryGetValue(mxid, out var rooms)) {
                span.End();
                return rooms.Select(hs.GetRoom).ToList();
            }
        }

        var userRooms = await hs.GetJoinedRooms();
        await updateLock.WaitAsync();
        CachedUserRooms[mxid] = userRooms.Select(r => r.RoomId).ToList();
        CachedUserRoomsExpiry[mxid] = DateTime.Now + TimeSpan.FromMinutes(5);
        updateLock.Release();
        span.End();
        return userRooms;
    }

    private async Task<KeyValuePair<string, AuthenticatedHomeserverGeneric?>> ValidateUser(string mxid, AuthenticatedHomeserverGeneric hs) {
        if(UserValidationExpiry.TryGetValue(mxid, out var expires))
            if(DateTime.Now < expires) return new KeyValuePair<string, AuthenticatedHomeserverGeneric?>(mxid, hs);
        var span = currentTransaction.StartSpan($"ValidateUser - {mxid}", ApiConstants.TypeApp);
        try {
            await hs.GetJoinedRooms();
            await updateLock.WaitAsync();
            UserValidationExpiry[mxid] = DateTime.Now + TimeSpan.FromMinutes(5);
            updateLock.Release();
            return new KeyValuePair<string, AuthenticatedHomeserverGeneric?>(mxid, hs);
        }
        catch (MatrixException e) {
            if (e.ErrorCode == "M_FORBIDDEN") {
                return new KeyValuePair<string, AuthenticatedHomeserverGeneric?>(mxid, null);
            }
            throw;
        }
        finally {
            span.End();
        }
    }
}