API reference — Integration
Nuvora.Nexus.Sentinel.Relay
Relay bridge for Sentinel: feeds Relay's AuthContext from the Sentinel principal, evaluates Relay [RequirePermission] attributes against Sentinel's authorization engine (grants, wildcards, deny-overrides), and maps the org claim onto Relay's TenantContext.
dotnet add package Nuvora.Nexus.Sentinel.Relay
Nuvora.Nexus.Sentinel.Relay.Authorization
SentinelAuthorizationPolicy
public sealed class SentinelAuthorizationPolicy : IAuthorizationPolicy
Relay IAuthorizationPolicy that evaluates a message’s [RequirePermission("service:scope:action")] against Sentinel’s authorization engine. Attach it with [RequirePolicy(SentinelAuthorizationPolicy.PolicyName)] next to the [RequirePermission] attribute.
How this hooks into Relay: AuthorizationService only consults IAuthorizationPolicy for [RequirePolicy]-decorated messages — its [RequirePermission] check is a claim lookup, which the bridge middleware already answers with engine-projected claims (see SentinelRelayPermissionCatalog). This policy is the belt to that suspender: a per-dispatch, straight-to-the-engine evaluation that (a) works even when the claim projection is absent, and (b) re-verifies that the projected AuthContext identity still matches the snapshot it claims to speak for. Relay’s behaviors invoke exactly one method per message kind — SentinelAuthorizationPolicy.EvaluateCommandAsync from AuthorizationBehavior, SentinelAuthorizationPolicy.EvaluateVoidCommandAsync from AuthorizationBehaviorVoid, SentinelAuthorizationPolicy.EvaluateQueryAsync from QueryAuthorizationBehavior — and all three funnel into the same evaluation here, so commands and queries get identical semantics.
Constructors
SentinelAuthorizationPolicy(IHttpContextAccessor httpContextAccessor)
Properties
string Name { get; }
The unique name this policy is referenced by from [RequirePolicy("...")] attributes
Methods
Task<AuthorizationResult> EvaluateCommandAsync<TCommand, TResponse>(AuthContext authContext, TCommand command, CancellationToken cancellationToken = default(CancellationToken))
Evaluates the authorization policy for a command with response
Task<AuthorizationResult> EvaluateQueryAsync<TQuery, TResponse>(AuthContext authContext, TQuery query, CancellationToken cancellationToken = default(CancellationToken))
Evaluates the authorization policy for a query
Task<AuthorizationResult> EvaluateVoidCommandAsync<TCommand>(AuthContext authContext, TCommand command, CancellationToken cancellationToken = default(CancellationToken))
Evaluates the authorization policy for a void command
Fields
const string PolicyName = "sentinel"
The name used in [RequirePolicy("sentinel")].
SentinelRelayPermissionCatalog
public sealed class SentinelRelayPermissionCatalog
The distinct set of permissions the application’s Relay messages demand via [RequirePermission("service:scope:action")], parsed as Sentinel PermissionIds.
Why this exists: Relay’s AuthorizationService enforces [RequirePermission] by exact string match against the permission claims on its AuthContext — it never calls out to an engine. Sentinel grants, however, are patterns (wildcards, deny-overrides, org/team/self scoping, ABAC conditions), so no static claim list can represent them. The bridge closes the gap by evaluating exactly this catalog against the subject’s snapshot on each request (SentinelRelayAuthContextMiddleware) and projecting the ALLOWED subset as permission claims. Relay’s exact-match check then agrees with Sentinel’s engine by construction. The catalog is enumerated from Relay’s own command/query registries — the same types AddRelay() scanned — so it can never drift from what the bus actually dispatches.
Contract consequence: when Sentinel is the authorization authority, every Relay permission string MUST use the Sentinel service:scope:action grammar. A non-conforming string is a configuration error and fails fast here, listing the offending message type, rather than silently never matching any grant.
Constructors
SentinelRelayPermissionCatalog(IServiceProvider services)
Properties
IReadOnlyList<PermissionId> RequiredPermissions { get; }
Every distinct permission demanded by a registered command or query.
Nuvora.Nexus.Sentinel.Relay.DependencyInjection
SentinelRelayServiceCollectionExtensions
public static class SentinelRelayServiceCollectionExtensions
Registration for the Sentinel → Relay bridge.
Methods
static IServiceCollection AddSentinelRelayAuthorization(this IServiceCollection services)
Wires Sentinel as Relay’s authorization authority: registers Relay’s auth services (accessor, policy provider, pipeline behaviors — the same set AddRelayAuth() installs), the permission catalog the bridge middleware projects from, and SentinelAuthorizationPolicy so [RequirePolicy("sentinel")] messages are evaluated per-dispatch against the Sentinel engine. Pair with app.UseSentinelRelayAuthContext() in the pipeline (which replaces UseRelayAuthContext() — see that extension’s remarks).
static IServiceCollection AddSentinelRelayTenancy(this IServiceCollection services, Action<RelayTenancyOptions>? configure = null)
Wires Sentinel’s org claim as Relay’s tenant: registers SentinelTenantResolver and Relay tenancy, with the claim source enabled. Add app.UseRelayTenantContext() after UseSentinelRelayAuthContext() so the resolver sees the authenticated principal.
Nuvora.Nexus.Sentinel.Relay.Middleware
SentinelRelayApplicationBuilderExtensions
public static class SentinelRelayApplicationBuilderExtensions
Pipeline wiring for the Sentinel → Relay auth-context bridge.
Methods
static IApplicationBuilder UseSentinelRelayAuthContext(this IApplicationBuilder app)
Adds SentinelRelayAuthContextMiddleware, projecting the Sentinel principal and its permission snapshot onto Relay’s AuthContext.
SentinelRelayAuthContextMiddleware
public sealed class SentinelRelayAuthContextMiddleware
Projects the request’s SentinelPrincipal (and its subject snapshot) onto Relay’s AuthContext via IAuthContextAccessor. This is the Sentinel-flavored replacement for Relay’s own RelayAuthorizationMiddleware: same pipeline slot, but the projection source is Sentinel’s typed principal and permission engine instead of generic RelayAuthOptions claim probes.
The mapping:
UserId←SentinelPrincipal.SubjectId. Sentinel subject ids are Guid v7 precisely so they satisfy Relay’s Guid-keyedAuthContext.UserId— no translation table, the ids line up by design.Username← the principal’s email/name claims when the token carries them; falls back to the subject id so logs always have a stable handle.Roles← always empty. Sentinel deliberately does not surface role NAMES on principals: roles, groups, and policies are compiled into grants inside the snapshot, so there is nothing role-shaped to project. Use[RequirePermission], not[RequireRole], when Sentinel is the authority.ClaimValues←realm,org,sid,mfafrom the typed principal, plus onepermissionclaim per catalog entry the Sentinel engine ALLOWS for this subject (seeSentinelRelayPermissionCatalogfor why projection — not interception — is how[RequirePermission]gets Sentinel semantics).
Constructors
SentinelRelayAuthContextMiddleware(RequestDelegate next, ILogger<SentinelRelayAuthContextMiddleware> logger)
Methods
Task InvokeAsync(HttpContext context)
Nuvora.Nexus.Sentinel.Relay.Tenancy
SentinelTenantResolver
public sealed class SentinelTenantResolver : ITenantResolver
Relay ITenantResolver that maps Sentinel’s org claim onto Relay’s TenantContext: the organization context minted into the access token IS the tenant. Registered for the TenantSource.Claim source ahead of Relay’s generic ClaimTenantResolver, and reads the typed SentinelPrincipal first — the validated token, not a spoofable header — falling back to the org claim on the mirrored HttpContext.User for hosts that authenticate Sentinel-minted JWTs through a different handler.
The org id is emitted as a Guid string; Relay’s TenantResolutionMiddleware turns it into TenantContext.For(orgId) via the registry or the Guid-identifier fallback (on by default), so no static tenant catalog is required. Org-less (realm-level) tokens yield null — “no tenant in scope” — and Relay’s [TenantScoped] enforcement then fails closed downstream, which is exactly the semantics of a token minted without an org context.
Properties
TenantSource Source { get; }
The source this resolver reads from.
Methods
string? Resolve(HttpContext context)
The raw identifier, or null when this source carries none.