tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
SessionInvalidationTracker.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Concurrent;
3using System.Linq;
4using System.Threading;
5using System.Threading.Tasks;
6
7using HotChocolate.Subscriptions;
8
9using Microsoft.Extensions.Hosting;
10using Microsoft.Extensions.Logging;
11
16
18{
21 {
25 readonly ITopicEventSender eventSender;
26
31
35 readonly IHostApplicationLifetime applicationLifetime;
36
40 readonly ILogger<SessionInvalidationTracker> logger;
41
45 readonly ConcurrentDictionary<(string SessionId, long UserId), TaskCompletionSource<SessionInvalidationReason>> trackedSessions;
46
55 ITopicEventSender eventSender,
57 IHostApplicationLifetime applicationLifetime,
58 ILogger<SessionInvalidationTracker> logger)
59 {
60 this.eventSender = eventSender ?? throw new ArgumentNullException(nameof(eventSender));
61 this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
62 this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
63 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
64
65 trackedSessions = new ConcurrentDictionary<(string, long), TaskCompletionSource<SessionInvalidationReason>>();
66 }
67
69 public void TrackSession(IAuthenticationContext authenticationContext)
70 {
71 trackedSessions.GetOrAdd(
72 (authenticationContext.SessionId, authenticationContext.User.Require(x => x.Id)),
73 tuple =>
74 {
75 var (localSessionId, localUserId) = tuple;
76 logger.LogTrace("Tracking session ID for user {userId}: {sessionId}", localUserId, localSessionId);
77 var tcs = new TaskCompletionSource<SessionInvalidationReason>();
78 async void SendInvalidationTopic()
79 {
80 try
81 {
82 SessionInvalidationReason invalidationReason;
83 try
84 {
85 var otherCancellationReason = tcs.Task;
86 var timeTillSessionExpiry = authenticationContext.SessionExpiry - DateTimeOffset.UtcNow;
87 if (timeTillSessionExpiry > TimeSpan.Zero)
88 {
89 var delayTask = asyncDelayer.Delay(timeTillSessionExpiry, applicationLifetime.ApplicationStopping).AsTask();
90
91 await Task.WhenAny(delayTask, otherCancellationReason);
92
93 if (delayTask.IsCompleted)
94 await delayTask;
95 }
96
97 invalidationReason = otherCancellationReason.IsCompleted
98 ? await otherCancellationReason
99 : SessionInvalidationReason.TokenExpired;
100
101 logger.LogTrace("Invalidating session ID {sessionID}: {reason}", localSessionId, invalidationReason);
102 }
103 catch (OperationCanceledException ex)
104 {
105 logger.LogTrace(ex, "Invalidating session ID {sessionID} due to server shutdown", localSessionId);
106 invalidationReason = SessionInvalidationReason.ServerShutdown;
107 }
108
109 var topicName = Subscription.SessionInvalidatedTopic(authenticationContext);
110 await eventSender.SendAsync(topicName, invalidationReason, CancellationToken.None); // DCT: Session close messages should always be sent
111 await eventSender.CompleteAsync(topicName);
112 }
113 catch (Exception ex)
114 {
115 logger.LogError(ex, "Error tracking session {sessionId}!", localSessionId);
116 }
117 }
118
119 SendInvalidationTopic();
120 return tcs;
121 });
122 }
123
126 {
127 ArgumentNullException.ThrowIfNull(user);
128
129 var userId = user.Require(x => x.Id);
130 user.LastPasswordUpdate = DateTimeOffset.UtcNow;
131
132 foreach (var key in trackedSessions
133 .Keys
134 .Where(key => key.UserId == userId)
135 .ToList())
136 if (trackedSessions.TryRemove(key, out var tcs))
137 tcs.TrySetResult(SessionInvalidationReason.UserUpdated);
138 }
139 }
140}
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SessionInvalidationTracker.
SessionInvalidationTracker(ITopicEventSender eventSender, IAsyncDelayer asyncDelayer, IHostApplicationLifetime applicationLifetime, ILogger< SessionInvalidationTracker > logger)
Initializes a new instance of the SessionInvalidationTracker class.
readonly ILogger< SessionInvalidationTracker > logger
The ILogger for the SessionInvalidationTracker.
readonly ITopicEventSender eventSender
The ITopicEventSender for the SessionInvalidationTracker.
readonly ConcurrentDictionary<(string SessionId, long UserId), TaskCompletionSource< SessionInvalidationReason > > trackedSessions
ConcurrentDictionary<TKey, TValue> of tracked IAuthenticationContext.SessionIds and User Api....
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SessionInvalidationTracker.
void TrackSession(IAuthenticationContext authenticationContext)
Track the session represented by a given authenticationContext .
void UserModifiedInvalidateSessions(User user)
Invalidate all sessions for a given user .
For creating and accessing authentication contexts.
string SessionId
A string that uniquely identifies the login session.
SessionInvalidationReason
Reasons TGS may invalidate a user's login session.