Skip to content

Releases

IBeam 2.17.0 — Management lifecycle and app operations

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 28 Oct 20:09
fe84786

IBeam 2.17.0 — Management lifecycle and app operations

IBeam 2.17.0 extends the Management API with ownership safety, independently authorized
commercial and app operations, tenant export/purge hooks, committed membership events,
safe transport telemetry and bounded invitation bulk revocation. Related packages remain
one .NET 10 release train; align all IBeam package references at 2.17.0.

Highlights

  • Independent last-owner and last-administrator protection, policy-bound role replacement,
    authoritative owner lookup and atomic ownership transfer with exact versions and durable receipts.
  • A typed registered-operation catalog and shared signed target sender. Fixed routes, schema
    fingerprints, live person/app/machine grants and separate support/customer realms remain enforced.
  • Explicit licence read/suspend/restore and seat read/assign/unassign operations replace borrowed
    tenant-status and membership permissions. Native eligibility, capacity and all-writer fencing apply.
  • Optional asynchronous tenant export and recoverable tenant purge. Apps implement their own data
    inventory and hooks; support receives safe progress/coverage metadata. Export delivery belongs to
    the authenticated customer. Purge preflight and confirmation bind the person, inventory and native
    version, preserve original unknown outcomes and require native/app access and write fences.
  • Durable native membership/role/ownership events with at-least-once delivery, bounded retries and
    dead-letter handling. App projections deduplicate original IDs and reconcile current native state
    behind their app write fence; delivery alone does not prove projection completion.
  • Metadata-only registered-operation audit and completion telemetry, including original operation
    IDs on uncertain writes. No keys, request/response bodies or provider exception text are exported.
  • Independently granted management.invites.bulk-revoke freezes 1–50 native invitations and exact
    versions into a durable batch. Bounded work, acceptance races, per-item outcomes and read-only
    receipts preserve safe same-intent recovery without sending invitation mail.
  • Package-only Management bootstrap with Development Swagger UI, accurate loaded-package version,
    optional portal composition and updated frontend migration instructions. Repeated synthetic setup
    preserves passwords, keys and changed/revoked access. The three-app running demo remains native
    Identity/ownership; lifecycle hooks are demonstrated by isolated synthetic integration fixtures.

New optional packages

  • IBeam.Management.Licensing
  • IBeam.Management.Operations.Target.Api
  • IBeam.Management.TenantData.Target.Services
  • IBeam.Management.TenantData.Target.Api
  • IBeam.Management.TenantData.Identity

Existing diagnostics-only and Identity-only compositions remain supported. Upgrading a package
does not enable a lifecycle endpoint, install a consumer data adapter or widen any existing grant.

Upgrade and activation

  1. Update related framework packages together to 2.17.0. The bootstrap project already pins this
    version and restores NuGet.org packages unless an explicit local validation feed is supplied.
  2. Deploy compatible conditional native stores and every participating request/write fence before
    enabling mutation capabilities. Custom providers must implement their declared durable protocols;
    missing/incompatible adapters fail closed. Preprovision dedicated tables and retain protection keys.
  3. Migrate existing owner metadata through trusted native setup, using immutable owner-role
    definitions and the chosen single/multiple-owner policy. Never infer ownership from the deprecated
    Tenants.OwnerUserId, widen a provisioning policy to transfer owners or delete persisted guards.
  4. Explicitly configure target enabled operations, backend credentials, support app assignments and
    delegation ceilings. Licence, seat, owner-transfer, export, purge and bulk-revoke grants are distinct.
  5. Implement app-owned exporter/purger/delivery/projection hooks and durable stores for the app's
    actual inventory. The synthetic samples are not production Qurvia data adapters. Export adds no
    support download endpoint; tenant purge does not imply deletion of shared global user identities.
  6. Preserve exact If-Match, original body/idempotency key and returned operation IDs/Locations after
    uncertain writes. Polling reads receipts only. Purge requires explicit human confirmation; trusted
    recovery must resume its original durable job. Retained/excluded coverage remains visible.
  7. Replace duplicate app-route signing with IManagementTargetOperationSender only after typed,
    allowlisted app descriptors and handlers are installed. Keep approved HTTPS/DNS/TLS policy.

See the frontend and lifecycle handoff,
ownership migration,
registered dispatch,
tenant export, tenant purge,
events,
projections and
bulk revoke.

Validation and release scope

The implementation epic completed 29 cards across seven features, 165 points. Feature-completion
validation passed 1,785 checks in 12 fresh generated-NuGet-only suites, 38 dependency packages,
24 inspected Management packages, an isolated two-target smoke, real support/customer MFA and
three-app ownership/enterprise workflows, and 45 new host/Swagger/optional-route checks.

The stable release adds versioned release-train packing, required main CI and a fresh public-feed
restore/build verification. Package manifests/checksums and validation evidence accompany the release.
Publishing framework packages does not deploy a consuming API/frontend or execute production
export, purge, ownership transfer or invitation changes. Those remain app-owned rollout actions.

IBeam 2.16.1 - Management invitation URL compatibility

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 27 Oct 17:37
a24646d

Management invitation links now work reliably as URL path segments behind Windows IIS.

  • Invitation resend, customer provisioning and enterprise onboarding share 256-bit, unpadded base64url token generation.
  • Existing padded-token recovery remains compatible. Stored token hashes and invitation records are not migrated or rewritten.
  • Includes focused regressions for all three generation paths and legacy recovery, plus NuGet-only consumer validation.

Keep related IBeam packages aligned at 2.16.1. Consumer APIs may enable encoded-plus path compatibility for older links; existing encoded-slash links should be resent after upgrading. The framework patch does not change IIS settings automatically.

This patch is based on the released 2.16.0 code and excludes unreleased development ownership/lifecycle changes. It includes no database migration.

Validation: 35 focused service/recovery tests, 1,081 NuGet consumer checks, and the required production build/test/pack check. The aligned release train contains 56 packages.

IBeam 2.16.0 — Enterprise tenant and customer management

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 20 Oct 17:46
9547bd1

IBeam 2.16.0 adds tenant and customer user management for enterprise onboarding through the Management API. Support users can administer their assigned applications while customer authentication, invitation acceptance and tenant data remain owned by each application backend.

Highlights

  • Two new optional packages: IBeam.Management.Identity.Target.Api and IBeam.Management.Identity.Target.Services.
  • Scoped tenant, customer membership and invitation directories, with bounded queries and protected continuation tokens.
  • Enterprise onboarding with a first administrator invitation, target-owned role and onboarding policy discovery, durable progress and optional consumer initialization.
  • Tenant creation, conditional rename and enable/disable; invitation-based customer provisioning, resend and revoke; membership status and role administration with last-administrator protection.
  • Durable idempotency receipts, conditional versions, reconciliation of uncertain outcomes and metadata-only audit records. Invitations are accepted by verified customer accounts through native Identity.
  • Live tenant and membership access enforcement for customer requests and refreshes, and trusted self-service/support-provisioned tenant provenance.
  • A portable, NuGet-only Management API bootstrap with three synthetic customer backends, real support/customer MFA, startup scripts and a frontend agent handoff prompt.

Upgrade and integration

  • Keep related IBeam packages aligned at 2.16.0. Packages target .NET 10.
  • Register the optional target packages in each customer Identity backend and the corresponding portal services/endpoints in the management host. Configure separate support and customer identity realms, target trust, machine capabilities and support app assignments before enabling customer operations.
  • Install native customer tenant/membership access enforcement before advertising status or membership management capabilities. Custom stores must implement the required conditional administration contracts; unsupported capabilities remain unavailable.
  • Customer onboarding uses invitations and customer-owned acceptance. It does not give support users customer sessions or access to business tables. Billing, Licensing and Credits remain optional.
  • Existing AddIBeamIdentityApi registration remains available. Hosts providing their own IIdentityCommunicationSender may register it first and use AddIBeamIdentityApiWithCommunicationSender.
  • The bootstrap pins 2.16.0 and can restore published packages from NuGet.org. A local package feed remains optional for validation. The frontend connects to the running Management API.
  • See the bootstrap, frontend handoff, and management guides.

Validation

  • Completed IBM-0098 through IBM-0117: 20 cards across eight features, 104 points.
  • Feature-completion validation passed 1,076 NuGet-only regression checks, seven temporary-host checks, and live support/customer MFA and enterprise onboarding smoke tests, including restart and access-revocation checks.
  • Release package validation results and package checksums are supplied with the release artifacts.

Production NuGet publication of all 56 packages succeeded. Stable package-only validation passed 1,076 tests, with zero failures/skips, and the real support/customer MFA enterprise bootstrap smoke passed. All 56 packages passed content/version inspection; the fresh transitive audit found zero vulnerable package occurrences. Production validation passed.

All 56/56 stable packages were verified in the public NuGet.org feed. A fresh-cache NuGet.org-only bootstrap restore and build passed, resolving all 30 IBeam dependencies at 2.16.0.

IBeam 2.15.0 — Management API

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 17 Oct 20:24
126f3ec

IBeam 2.15.0 introduces the Management API packages for a support portal that can manage access to multiple applications and read IBeam system errors and operational logs.

Added

  • Eight new packages: IBeam.Management, IBeam.Management.Services, IBeam.Management.Api, IBeam.Management.Diagnostics.Api, IBeam.Management.Repositories.AzureTable, IBeam.Management.Identity.Services, IBeam.Management.Identity.Api, and IBeam.Management.Secrets.AzureKeyVault.
  • Isolated support identity with MFA, invitation onboarding, session revocation, and either a separate storage account or a distinct management realm in an existing account.
  • Organization-scoped application registration, verified backend connections, backend-only secret references, and support-user assignments to one or many applications. One support session can select among its authorized applications.
  • Read-only system error and operational log endpoints with independent machine-credential capabilities, bounded queries, protected paging and record identifiers, and live access checks.
  • Metadata-only management auditing and explicit diagnostic source isolation. Management diagnostics do not expose tenant business tables or customer audit records.
  • Independent error and log sanitization settings under IBeam:Management:Diagnostics:{Errors|Logs}:Sanitization:Enabled. Sanitization defaults to enabled. Explicitly disabling it is supported only for recognized non-Production host and deployment environments; the response labels raw fields in unsanitized.
  • Deployment, source-isolation, and sanitization guides, plus a package-only consumer example and repeatable package validation script.

Fixed

  • Pinned the Entity Framework adapter's transitive System.Security.Cryptography.Pkcs dependency to 9.0.21, addressing GHSA-846f-f764-76mx and GHSA-2fmv-rv2c-59x7.
  • Aligned RC NuGet publishing with the existing production trusted-publisher environment.
  • Completed password MFA authentication now includes the amr=mfa claim used by support-session enforcement.
  • Removed app-specific values and a committed communication credential from the example Identity API configuration. Supply deployment values through configuration and a secret store.

Upgrading

  • Keep related IBeam NuGet packages aligned at 2.15.0. Packages target .NET 10.
  • Management features are opt-in. Register the portal packages in the management host and diagnostics packages in each application backend. Configure isolated identity/storage, deployment-bound credentials, source ownership, secret references, and user assignments before enabling access.
  • Keep sanitization enabled in Production. The explicit non-Production raw mode does not widen source access, credential capabilities, or application assignments.
  • The standalone portal host/UI and the IBeam.Admin.API rename/deployment remain separate consumer work.
  • See deployment and handoff, diagnostic source isolation, and diagnostic sanitization.

Validation

  • Development build, test, pack, and development package publishing passed on commit 206fbd5.
  • Management and shared-component regression matrix: 756 passed, 0 failed, 0 skipped, using private Table fixtures.
  • Package-only two-application consumer validation passed, including inspection of all eight new packages and a generated dependency feed of 20 packages.

Management API epic: IBM-0078 through IBM-0097; 20 completed cards across 10 features, 107 points.

v2.14.0

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 28 Sep 23:13

Three security fixes with one theme: configuration now means what it says. All three are breaking for apps that configure the affected settings; apps that configure nothing are unaffected.

Security

  • Configuration can now narrow IBeam:Identity:AccessControl role and permission lists, not only widen them. IConfiguration.Bind adds to a collection rather than replacing it, so every list on IBeamAccessControlOptions — which defines who is an owner or an administrator — kept its built-in names no matter what an app configured. An app that set AdminRoleNames to ["RegionalAdmin"] in order to restrict administrator access still treated anyone holding a role named Administrator or Admin as an administrator, with no warning that its configuration had not taken effect.
  • OAuth clients configured with explicit AllowedGrantTypes no longer silently also allow authorization_code. A client registered for the device-code grant alone previously permitted both.
  • Tenant invite links are restricted to an allowlist of origins. DefaultTenantInviteUrlBuilder built the "Accept invitation" link from whatever RedirectUrl the caller supplied, with no validation, so anyone who could create an invite could send a genuine, app-branded invitation whose button opened a site of their choosing. Separately and independently, DefaultTenantInviteMessageFactory copied caller-supplied Metadata over its own values, so metadata: { "inviteUrl": "https://evil.example" } replaced the link even when the redirect URL was valid — and because that dictionary was case-sensitive, InviteUrl reached the template model alongside inviteUrl. Both paths are closed.

Changed

  • Breaking for apps that configure access-control lists. If your app both configures a list under IBeam:Identity:AccessControl and relies on IBeam's built-in values still being present, add those values to your configuration explicitly. The lists: OwnerRoleNames, AdminRoleNames, ApplicationRoleNames, TenantManagementPermissionNames, TenantUserManagementPermissionNames, TenantRoleManagementPermissionNames, TenantAccessControlManagementPermissionNames, ApiCredentialManagementPermissionNames, OAuthClientManagementPermissionNames, AuthAttemptManagementRoleNames, AuthAttemptManagementPermissionNames, AccessLevels.
  • Breaking for apps that configure OAuth client grant types. A configured AllowedGrantTypes now means exactly those grants. An app relying on the implicit authorization_code alongside its configured grants must list it.
  • Breaking for apps that send tenant invites. Configure IBeam:Identity:Invites:Links:DefaultAcceptUrl with your invite accept page, and list your front-end origins under AllowedOrigins. The previous hardcoded https://localhost:3000/invites/accept fallback is gone — an app that sends an invite without configuring a destination now fails with a message naming the setting.
  • An OAuth client that specifies no grant types in configuration continues to default to authorization_code, per RFC 7591 §2. Registering a client programmatically or through the administration API with an explicitly empty grant list is still rejected — configuration cannot express an empty list, but a caller can, and meaning it should not silently earn a grant.

Added — invite link control

"IBeam:Identity:Invites:Links": {
  "AllowedOrigins": [ "https://app.example.com" ],  // exact scheme://host[:port], no wildcards
  "DefaultAcceptUrl": "https://app.example.com/invites/accept",
  "AcceptPath": "/invites/accept",                  // discards the caller's path on an allowed origin
  "OnDisallowed": "Fallback"                        // or "Reject" to refuse the invite
}

Configurable in code as well, which is the point rather than a convenience — AddIBeamTenantInviteLinks's delegate runs after configuration binding, so an app can build the allowlist from its own CORS origins rather than repeating them:

services.AddIBeamTenantInviteLinks(o =>
{
    foreach (var origin in myCorsOrigins) o.AllowedOrigins.Add(origin);

    // For rules a list cannot express — per-tenant custom domains, preview environments.
    // Consulted only after the allowlist says no, and only for a structurally valid URL.
    o.IsAllowed = (uri, invite) => uri.Host.EndsWith(".preview.example.com");
});

ITenantInviteLinkPolicy is TryAddScoped, so it can be replaced outright. AllowAnyOrigin() restores the pre-2.14.0 behaviour deliberately and logs a warning each time it is used.

Do not populate AllowedOrigins from a CORS wildcard. An app trusting *.example-hosting.net for CORS is trusting every tenant of that host.

Upgrading

  1. Access control. Search appsettings*.json for IBeam:Identity:AccessControl. If you configure any of the listed lists, that list is now exactly what you wrote — add back any IBeam default you were relying on.
  2. OAuth grants. Search for AllowedGrantTypes. If a client configures it and also expects authorization_code, add it.
  3. Invites. If your app sends tenant invites, configure DefaultAcceptUrl and AllowedOrigins before deploying. This one fails loudly rather than silently, so it will be obvious — but it will be obvious at the moment someone sends an invite.
  4. Apps that configure none of the three need no changes.

The direction of the access-control change matters: before this release, configuration could only ever grant more access than it appeared to. An app unknowingly relying on the old behaviour will find access narrowing on upgrade rather than widening — a failure that is visible, not silent.

Why a minor version

Three breaking changes would ordinarily argue for a major bump. Released as a minor because the blast radius is bounded: each one affects only apps that configure the specific setting involved. Anyone who does should read Upgrading rather than treating this as routine.

Validation

Verified on the released commit (d128420), not on a development branch:

  • 676 tests passed across 31 suites, 0 failed, 1 skipped.
  • New coverage that binds options from IConfiguration and asserts the bound values equal exactly what was configured — the gap that let the first two bugs survive: AccessControlOptionsConfigurationTests, OAuthAuthorizationServerOptionsTests, TenantInviteLinkPolicyTests.
  • The invite tests were checked against the unfixed code: reverting the URL builder fails 1, reverting the metadata merge order fails 2. One test gates the wiring specifically, because the policy tests alone would have passed with a correct policy that nothing used.

Known consumers

  • Bindry has already adapted to the access-control change (BIND-0214), and carries its own app-level invite-link fix (BIND-0124) which it can now delete in favour of registering IBeam's policy with its real CORS origins. It is pinned to IBeamVersion 2.12.2, so bumping crosses 2.12.3, 2.13.0, 2.13.1 and this release together.
  • Every other IBeam consumer should be checked against the Upgrading steps. IBeam is the shared identity layer across seven-plus products, and this release touches who counts as an administrator and where invite emails point.

IBM-0068 · IBM-0072 · IBM-0073

v2.13.1

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 28 Sep 14:05

Supersedes v2.13.0, which was accidentally cut from main's prior tip before the release branch was promoted, and does not contain the change below.

Added

  • OAuth device authorization grant (RFC 8628) — enables browser-less sign-in for CLIs and other input-constrained clients (BIND-0202).
    • IOAuthDeviceAuthorizationStore with an Azure Table backend. The Entity Framework backend gets an explicit not-implemented stub, so every other OAuth grant (authorization_code, refresh_token, client_credentials) keeps working unchanged for EF-backed deployments; only a device_code request against EF fails, with a clear error.
    • New /oauth/device_authorization and /oauth/device endpoints, and a device_code case in OAuthTokenService.ExchangeAsync.
    • OAuthClientRecord.DeviceVerificationUri — a per-client verification URL, alongside the existing RedirectUris.
    • OAuthConfiguredClientSeeder — idempotently syncs IBeam:Identity:OAuthServer:Clients config into whichever IOAuthClientStore is registered, so a first-party, cross-tenant client (e.g. a CLI) can be declared without going through the tenant-scoped client admin API.

v2.13.0

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 25 Sep 20:14

Added

  • OAuth device authorization grant (RFC 8628) — enables browser-less sign-in for CLIs and other input-constrained clients (BIND-0202).
    • IOAuthDeviceAuthorizationStore with an Azure Table backend. The Entity Framework backend gets an explicit not-implemented stub, so every other OAuth grant (authorization_code, refresh_token, client_credentials) keeps working unchanged for EF-backed deployments; only a device_code request against EF fails, with a clear error.
    • New /oauth/device_authorization and /oauth/device endpoints, and a device_code case in OAuthTokenService.ExchangeAsync.
    • OAuthClientRecord.DeviceVerificationUri — a per-client verification URL, alongside the existing RedirectUris.
    • OAuthConfiguredClientSeeder — idempotently syncs IBeam:Identity:OAuthServer:Clients config into whichever IOAuthClientStore is registered, so a first-party, cross-tenant client (e.g. a CLI) can be declared without going through the tenant-scoped client admin API.

v2.12.3

Choose a tag to compare

@mp-christian-armstrong mp-christian-armstrong released this 18 Sep 19:10

IBeam v2.12.3

Completes the provider-webhook fix from v2.12.2 for hosts that use licensed
service operations. One commit: b3e9b98 (PR #24).

Provider webhooks still failed under AddIBeamLicensedServiceOperations()

v2.12.2 (ee1e8f8) let a verified provider callback run without tenant
authorization. BillingWebhookProcessor opens an IServiceOperationSystemContext
scope after signature verification, and ServiceOperationExecutor skips the
tenant demand inside it.

That fix never reached hosts that call AddIBeamLicensedServiceOperations().
The call replaces the executor with LicensedServiceOperationExecutor, which
builds its own inner ServiceOperationExecutor and passed it no system
context. The scope was open, but the executor doing the authorization check
couldn't see it. So every webhook still returned 500 with
"tenantId is required for service operation authorization", the failure
v2.12.2 set out to remove. In Qurvia, 21 of 21 Stripe sandbox events failed on
v2.12.2.

What changed

  • LicensedServiceOperationExecutor takes IServiceOperationSystemContext as
    an optional constructor parameter and passes it to its inner executor.
  • Its licence check is skipped only while the scope is active. A verified
    webhook has no licence subject and no tenant, so demanding a licence fails
    the same way. This matters because a host's IBillingPaidPurchaseHandlers run
    inside ProcessAsync and can call licensed operations. Outside the scope,
    every caller is licence-checked exactly as before, including admin and
    support tooling that calls the same operations.
  • AddIBeamLicensedServiceOperations() now also registers the context
    (scoped, via TryAdd). The processor and the executor share one instance per
    request, even in a host that never called AddIBeamServicePolicies.

The design is unchanged from v2.12.2: a request-scoped exemption, not
Permission = false or a licence-free operation list. Annotating operations
would also exempt authenticated callers, and would need a new annotation every
time the webhook path grew.

Why it was not caught

The processor tests built their services by hand with the plain executor,
which does honour the scope. The composition production uses was never
exercised: same code, different container. Both halves of this fix are needed.
With only the context pass-through, the new tests still fail, this time on the
licence check.

Tests

  • New LicensedWebhookCompositionTests: builds the container the way a host
    does (billing, licensing, AddIBeamLicensedServiceOperations() and a
    denying authorizer) and runs a signed event end to end. All four cases fail
    on v2.12.2 with the production exception, and pass on v2.12.3.
  • Executor unit tests: inside and outside a system scope, plus an
    authenticated caller with no licence (still denied).
  • Full suite: 594 passed, 2 failed. The 2 failures are OAuth tests that
    already failed before this change (587 passed, same 2 failed).

Upgrade notes

  • Additive only. The new constructor parameter is optional and last, so
    existing direct construction of LicensedServiceOperationExecutor compiles
    and behaves as before.
  • Hosts on v2.12.2 that call AddIBeamLicensedServiceOperations():
    webhooks start succeeding with no code change beyond the package bump.
  • Remove any workaround that caught AccessControlException /
    UnauthorizedAccessException around webhook recording (Qurvia's QUR-0261
    catch in PaymentWebhookService). With this release, IBeam records provider
    events and runs its own paid-purchase handlers again. Verified against
    Qurvia with its workaround removed: webhook tests 7/7, including a signed
    commerce purchase reaching paid.
  • Hosts that don't use licensed service operations are unaffected.

v2.12.2

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 11 Sep 14:29

IBeam v2.12.2

Fixes provider webhooks failing in any host that registers service-operation
authorization. One commit: ee1e8f8.

Provider webhooks no longer fail authorization

Every provider webhook returned 500 before it could handle a payload.

Service-operation authorization asks two things: which principal is acting, and
in which tenant. A payment processor can answer neither — it cannot sign in to
the consuming app, and the tenant is not known until the payload has been read.
So BillingProviderEventService.RecordEventAsync threw
"tenantId is required for service operation authorization" on the way in, and
recording happens on every outcome, including the ignore branches, so no
payload got through.

This contradicted IBeam.Billing.Api's own documented design: "Provider
webhooks are anonymous because processor callbacks cannot sign in to the
consuming app."

It only affects hosts that register an IServiceOperationAuthorizer — the
demand short-circuits when none is present, which is presumably why it shipped.

What changed

IServiceOperationSystemContext (new, in IBeam.AccessControl) marks a stretch
of work as originating from a verified machine callback.
ServiceOperationExecutor honours it by skipping the tenant demand.
BillingWebhookProcessor opens one — strictly after signature verification,
never before
. The signature over the raw body is what establishes that the
request is genuine; the scope only suppresses a check that cannot apply to such
a caller, and proves nothing on its own.

Registered scoped by AddIBeamServicePolicies and AddIBeamServiceAuditing, so
an exemption cannot outlive the request that opened it even if a handle is never
disposed. Nesting is supported, and a double dispose cannot pop an outer scope.
Enter() requires a reason, so an exemption never appears unexplained.

Why a scope rather than Permission = false

Three operations on the webhook path carry no tenant —
billing.provider-events.record, billing.purchases.get and
billing.purchases.apply-provider-update — plus whatever a host's own
IBillingPaidPurchaseHandler calls, since that runs inside ProcessAsync too.
Annotating each would have exempted the authenticated admin and support tooling
that reaches the same services, and would have needed a new annotation every
time the path grew.

Why it was not caught

Mock billing reaches the same processor from inside an authenticated request,
where ITenantContext is populated and the demand is satisfied. Same code,
different caller — so it passed every test that used the mock gateway.

Upgrade notes

  • IServiceOperationSystemContext is new and registered automatically by
    AddIBeamServicePolicies / AddIBeamServiceAuditing. Hosts calling neither
    are unaffected; the executor treats it as optional.
  • ServiceOperationExecutor and BillingWebhookProcessor each take one new
    optional constructor parameter. Existing construction, including in tests,
    is unaffected.
  • No configuration change is required.

Verification

All 31 test projects pass on main (70e0675), rebuilt and re-run after the
promotion rather than only on the feature branch.

New coverage pins the security boundary rather than the happy path:

  • no tenant and no scope still refuses — if that ever passes, an
    unauthenticated caller would sail through every service operation;
  • authorization resumes once the scope closes;
  • a double dispose cannot pop an outer scope;
  • Enter() rejects a blank reason.

v2.12.1

Choose a tag to compare

@mp-abram-cookson mp-abram-cookson released this 11 Sep 01:43

Fixes a bug in v2.12.0's "remember this device" feature: the real Azure Table session store never persisted the new Remembered field, so a remembered session's long refresh window silently reverted to the normal one on its very first refresh. Fixed AuthSessionEntity/AzureTableAuthSessionStore to carry the field through, verified with a real round-trip test against Azurite.