feat(sessions): record OwnerKeyId on session creation

Add a nullable string? OwnerKeyId property to GatewaySession that captures
the API key identifier (KeyId) of the authenticated caller that opened the
session. Wire it through ISessionManager.OpenSessionAsync → SessionManager
→ GatewaySession constructor. The gRPC service passes identityAccessor
.Current?.KeyId; internal callers (GatewayAlarmMonitor, DashboardLiveDataService)
pass null. Covers the positive and null cases with two new TDD-first tests.
This commit is contained in:
Joseph Doherty
2026-06-15 12:24:29 -04:00
parent 00c849e63b
commit f5479f3ca3
16 changed files with 88 additions and 24 deletions
@@ -203,6 +203,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
GatewaySession session = await _sessionManager.OpenSessionAsync(
new SessionOpenRequest(BackendName, MonitorClientName, Guid.NewGuid().ToString("N"), CommandTimeout: null),
MonitorClientName,
ownerKeyId: null,
stoppingToken)
.ConfigureAwait(false);
lock (_sync) { _session = session; }
@@ -138,6 +138,7 @@ public sealed class DashboardLiveDataService : IDashboardLiveDataService, IAsync
GatewaySession session = await _sessionManager.OpenSessionAsync(
new SessionOpenRequest(BackendName, ClientName, Guid.NewGuid().ToString("N"), CommandTimeout: null),
ClientName,
ownerKeyId: null,
cancellationToken)
.ConfigureAwait(false);
@@ -36,6 +36,7 @@ public sealed class MxAccessGatewayService(
.OpenSessionAsync(
SessionOpenRequest.FromContract(request),
ResolveClientIdentity(),
identityAccessor.Current?.KeyId,
context.CancellationToken)
.ConfigureAwait(false);
@@ -48,6 +48,7 @@ public sealed class GatewaySession
pipeName,
nonce,
clientIdentity,
ownerKeyId: null,
clientSessionName,
clientCorrelationId,
commandTimeout,
@@ -66,6 +67,7 @@ public sealed class GatewaySession
/// <param name="pipeName">Name of the named pipe for gateway-worker IPC.</param>
/// <param name="nonce">Security nonce for worker validation.</param>
/// <param name="clientIdentity">Client identity from the authentication context.</param>
/// <param name="ownerKeyId">API key identifier of the caller that created this session.</param>
/// <param name="clientSessionName">Client-supplied session name.</param>
/// <param name="clientCorrelationId">Client-supplied correlation identifier.</param>
/// <param name="commandTimeout">Timeout for command invocation.</param>
@@ -79,6 +81,7 @@ public sealed class GatewaySession
string pipeName,
string nonce,
string? clientIdentity,
string? ownerKeyId,
string? clientSessionName,
string? clientCorrelationId,
TimeSpan commandTimeout,
@@ -112,6 +115,7 @@ public sealed class GatewaySession
PipeName = pipeName;
Nonce = nonce;
ClientIdentity = clientIdentity;
OwnerKeyId = ownerKeyId;
ClientSessionName = clientSessionName;
ClientCorrelationId = clientCorrelationId;
CommandTimeout = commandTimeout;
@@ -148,6 +152,11 @@ public sealed class GatewaySession
/// </summary>
public string? ClientIdentity { get; }
/// <summary>
/// Gets the API key identifier of the caller that created this session.
/// </summary>
public string? OwnerKeyId { get; }
/// <summary>
/// Gets the client-supplied session name.
/// </summary>
@@ -8,11 +8,13 @@ public interface ISessionManager
/// <summary>Opens a new gateway session and launches a worker process.</summary>
/// <param name="request">Request payload.</param>
/// <param name="clientIdentity">Client identity string.</param>
/// <param name="ownerKeyId">API key identifier of the caller creating the session.</param>
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
/// <returns>The newly opened session.</returns>
Task<GatewaySession> OpenSessionAsync(
SessionOpenRequest request,
string? clientIdentity,
string? ownerKeyId,
CancellationToken cancellationToken);
/// <summary>Attempts to retrieve a session by ID.</summary>
@@ -58,11 +58,13 @@ public sealed class SessionManager : ISessionManager
/// </summary>
/// <param name="request">Session open request.</param>
/// <param name="clientIdentity">Client authentication identity.</param>
/// <param name="ownerKeyId">API key identifier of the caller creating the session.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Opened gateway session.</returns>
public async Task<GatewaySession> OpenSessionAsync(
SessionOpenRequest request,
string? clientIdentity,
string? ownerKeyId,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
@@ -72,7 +74,7 @@ public sealed class SessionManager : ISessionManager
bool sessionOpenedRecorded = false;
try
{
session = CreateSession(request, clientIdentity);
session = CreateSession(request, clientIdentity, ownerKeyId);
if (!_registry.TryAdd(session))
{
throw new SessionManagerException(
@@ -420,7 +422,8 @@ public sealed class SessionManager : ISessionManager
private GatewaySession CreateSession(
SessionOpenRequest request,
string? clientIdentity)
string? clientIdentity,
string? ownerKeyId)
{
string sessionId = CreateSessionId();
string backendName = string.IsNullOrWhiteSpace(request.RequestedBackend)
@@ -441,6 +444,7 @@ public sealed class SessionManager : ISessionManager
pipeName,
nonce,
clientIdentity,
ownerKeyId,
request.ClientSessionName,
clientCorrelationId,
commandTimeout,