Add method-level authorization via [NexusAuthorize<TPermission>]#65
Merged
Add method-level authorization via [NexusAuthorize<TPermission>]#65
Conversation
Introduces AuthorizeResult enum, NexusAuthorizeAttribute<TPermission>, ProxyUnauthorizedException, OnAuthorize virtual on ServerNexusBase, Unauthorized state on InvocationResultMessage, and Unauthorized disconnect reason. This is the runtime foundation for generator-emitted auth guards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generator extracts [NexusAuthorize<TPermission>] from class methods, correlates with interface methods, emits static permission arrays and auth guards in InvokeMethodCore switch cases. Adds NEXNET024-026 diagnostics for client usage, missing OnAuthorize override, and mixed permission enum types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generator tests verify diagnostics (NEXNET024-026) and successful code generation for authorized, marker-only, and multi-permission methods. Integration tests cover Allowed/Unauthorized/Disconnect flows, parameter verification, return values, method body non-execution, and exception handling across all 6 transport types (66 tests). Refactored ServerNexusBase to expose a single protected CheckAuthorization method that handles the full auth lifecycle (authorize, send unauthorized result, disconnect) internally, keeping generated code minimal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The pinned libmsquic 2.4.8 was removed from Microsoft's Ubuntu 24.04 apt repository. Install latest available version instead and run apt-get update first to ensure fresh package index. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Using [NexusAuthorize] without overriding OnAuthorize now fails compilation, ensuring fail-closed authorization by default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend [NexusAuthorize<TPermission>] to work on collection properties alongside methods. The auth guard is emitted before StartCollection in InvokeMethodCore, so every collection connect/reconnect is authorized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap InvocationResultMessage rent/send/dispose in try/finally in
CheckAuthorization to prevent pool leak if SendMessage throws
- Add NEXNET027 compile error when permission enum underlying type is
Int64/UInt64 (values stored as Int32 would silently overflow)
- Add integration test verifying multiple permissions on a single method
are correctly passed through to OnAuthorize
- Fix comment typo in MessageType.cs ("Reserved through -39" → "39")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-session caching of authorization results with configurable TTL at two levels: - ServerConfig.AuthorizationCacheDuration: server-wide default (null = disabled) - NexusAuthorizeAttribute.CacheDurationSeconds: per-method/collection override (-1 = use server config, 0 = never cache, >0 = seconds) Method attribute always wins when set. Only Allowed and Unauthorized results are cached; Disconnect and exception paths are never cached. Includes InvalidateAuthorizationCache() overloads for explicit cache clearing (all entries or by method ID), and an internal TickCountOverride for deterministic test timing without Task.Delay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… perm fields by ID - Replace Dictionary with ConcurrentDictionary for thread-safe concurrent access to the authorization cache - Whitelist int-only for permission enum underlying type (NEXNET027) instead of blacklisting long/ulong, catching uint/byte/etc. - Key generated __authPerms_ fields by method/collection ID instead of name to prevent collisions from method overloads - Add concurrent cache access integration test using ManualResetEventSlim to force thread contention - Add byte-backed enum generator test - Fix test name AuthorizeWithoutOnAuthorize_EmitsWarning -> _EmitsError Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add comprehensive authorization documentation covering setup, authorization results, caching configuration, and compile-time diagnostics. Update llm-dev.md with new types, models, and test files. Update llm-usage.md with usage examples and config table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
[NexusAuthorize<TPermission>]attribute support for authorization on server nexus methods and synchronized collections. The source generator emits auth guards inInvokeMethodCorethat call a virtualOnAuthorizemethod before any argument deserialization or method/collection execution. Users define a permission enum, decorate server nexus methods or collection properties with[NexusAuthorize<TPermission>(...)], and overrideOnAuthorizefor custom authorization logic.Method example:
Collection example:
Collection authorization is enforced on every connect/reconnect since all collection connections go through
InvokeMethodCore. Reconnecting clients are re-authorized automatically.Authorization result caching
Authorization results can be cached per-session with configurable TTL at two levels:
Only
AllowedandUnauthorizedresults are cached.Disconnectand exception paths are never cached. Cache is per-nexus instance (per session) and invalidated automatically on reconnection. Explicit invalidation is available viaInvalidateAuthorizationCache().Reason for the change
NexNet had no built-in authorization mechanism. Users needing per-method or per-collection access control had to implement manual checks, which is error-prone, repetitive, and happens after argument deserialization (wasted work for unauthorized calls). This feature provides a declarative, generator-driven approach with compile-time type safety for both methods and collections.
Impacts of changes
AuthorizeResultenum,NexusAuthorizeAttribute<TPermission>,ProxyUnauthorizedExceptionNexusAuthorizeAttributenow targetsMethod | Property(wasMethodonly), gainsCacheDurationSecondspropertyServerNexusBase<TProxy>gainsOnAuthorizevirtual,CheckAuthorizationprotected method,InvalidateAuthorizationCache()overloads, and per-session auth cacheServerConfiggainsAuthorizationCacheDurationpropertyInvocationResultMessage.StateType.Unauthorized(value 3), newMessageType.DisconnectUnauthorized(value 34), newDisconnectReason.Unauthorized[NexusAuthorize<>]from class methods and interface collection properties, emits static permission arrays and auth guards for both, adds 4 new diagnostics (NEXNET024-027) covering methods and collectionsCollectionDatagainsAuthorizeData?field;ExtractAuthorizeDatarefactored to shared helper supporting bothIMethodSymbolandIPropertySymbolProxyInvocationBasenow handlesUnauthorizedstate by throwingProxyUnauthorizedExceptionMigration Steps
None required. This is a purely additive feature. Existing code continues to work without modification. To adopt:
int(the default) -- non-intunderlying types are rejected at compile time via NEXNET027[NexusAuthorize<TPermission>(...)]to server nexus class methods and/or interface collection propertiesOnAuthorizeon the server nexus classProxyUnauthorizedExceptionon the client side where needed (methods only -- collections useDisconnectfor unauthorized access since they lack a return channel)ServerConfig.AuthorizationCacheDurationor per-methodCacheDurationSecondsfor cachingPerformance Considerations
[NexusAuthorize]OnAuthorizereturningValueTask<AuthorizeResult>with a synchronous result allocates nothingint[]arrays are emitted asstatic readonlyclass fields, allocated onceOnAuthorizecalls for hot paths; thread-safeConcurrentDictionary<int, ...>per session keyed by method ID with lock-free readsSecurity Considerations
OnAuthorizethrows, the session is disconnected (logged as error). This prevents accidental authorization bypass from buggy auth logicInvokeMethodCoreand are re-authorized[NexusAuthorize]is used butOnAuthorizeis not overridden (default allows all). Applies to both methods and collectionsint(the default), since permission values are stored asInt32InvocationResultMessageis disposed in atry/finallyblock to prevent pool leaks ifSendMessagethrowsAllowedandUnauthorizedare cached;Disconnectand exception paths are never cached. Cache is per-session with explicit invalidation support.CacheDurationSeconds = 0provides an explicit no-cache override for sensitive methodsAuthorizeResult.Unauthorizedon a collection silently drops the request since collections lack a return channel. UseAuthorizeResult.Disconnectto actively reject unauthorized collection accessDiagnostics
[NexusAuthorize]used on a client nexus (server-only)[NexusAuthorize]used butOnAuthorizenot overridden[NexusAuthorize]attributesint(the default)Breaking changes
Public consumer-facing changes
None. All additions are backward-compatible:
AuthorizeResult,NexusAuthorizeAttribute<T>,ProxyUnauthorizedExceptionare new typesOnAuthorizevirtual has a default implementation returningAllowedDisconnectReason.Unauthorizedis a new enum member (underlying byte value 34)ServerConfig.AuthorizationCacheDurationdefaults tonull(disabled)NexusAuthorizeAttribute.CacheDurationSecondsdefaults to-1(use server config)Internal non-consumer changes
InvocationResultMessage.StateTypegainsUnauthorized = 3-- older clients receiving this from a newer server will hit thedefaultswitch case and throwInvalidOperationException. This only occurs if the feature is actively used against an older client.ProxyRemoteInvocationExceptiongains aprotectedconstructor accepting astring messageparameter (forProxyUnauthorizedExceptioninheritance). This is binary-compatible but a minor API surface addition.MessageType.DisconnectUnauthorized = 34-- older peers receiving this will not recognize the disconnect reason, but will still disconnect cleanly as the transport closes.