using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
///
/// SignalR hub for per-session MxEvent push. Clients call
/// to join the group for a specific
/// session; sends messages to
/// session:{id} as events arrive from the live gRPC stream.
///
[Authorize(Policy = DashboardAuthenticationDefaults.HubClientsPolicy)]
public sealed class EventsHub : Hub
{
/// Method name used to push individual MxEvent values to clients.
public const string EventMessage = "MxEvent";
/// Computes the SignalR group name for a given session id.
/// The session id.
/// The group name for the session.
public static string GroupName(string sessionId) => $"session:{sessionId}";
///
/// Subscribes the calling SignalR connection to the per-session events
/// group, so that events broadcast by
/// for that session reach this
/// client.
///
///
/// In v1 the hub-level
/// (HubClientsPolicy) only checks that the caller carries one of
/// the dashboard roles (Admin or Viewer); both roles may subscribe to
/// any session id they choose. This is acceptable today because (a) the
/// dashboard's per-session views show non-secret session metadata that
/// any authenticated dashboard user can already see, and (b) tag values
/// are stripped from the mirrored events by
/// when
/// MxGateway:Dashboard:ShowTagValues is false (the default), so the
/// most sensitive payload cannot leak through this seam regardless of the
/// still-missing ACL. The per-session ACL that gates the gRPC
/// StreamEvents RPC is intentionally not yet mirrored here.
/// TODO(per-session-acl): tracked as remediation roadmap item 12
/// (SEC-25). Once a role/scope is introduced that scopes a Viewer to a
/// specific session or tenant, add a session-access check at this seam —
/// either inline (consult the per-user allowed-session set on
/// Context.User claims / Context.Items) or via a dedicated
/// authorization policy applied to the hub method itself.
///
/// Session id to subscribe the caller to.
/// A task representing the subscription operation.
public Task SubscribeSession(string sessionId)
{
if (string.IsNullOrWhiteSpace(sessionId))
{
return Task.CompletedTask;
}
return Groups.AddToGroupAsync(Context.ConnectionId, GroupName(sessionId));
}
/// Unsubscribes the calling SignalR connection from the per-session events group.
/// Session id to unsubscribe the caller from.
/// A task representing the unsubscription operation.
public Task UnsubscribeSession(string sessionId)
{
if (string.IsNullOrWhiteSpace(sessionId))
{
return Task.CompletedTask;
}
return Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName(sessionId));
}
}