Skip to content
fullstackhero

Reference

Shared building block

Multi-tenancy types, permission registry, claim/role/action/resource constants, audit attributes, and shared DTOs used by every module.

views 0 Last updated

The Shared block carries the typed constants and DTOs every module references - tenant info, the permission registry, claim/action/resource string constants, audit attributes, and the DatabaseOptions consumed by Persistence. It exists so modules can reference one block of shared types instead of either copy-pasting constants or pulling in the heavier Web / Persistence runtime blocks.

What it ships

Multitenancy

  • AppTenantInfo - extends Finbuckle’s TenantInfo + implements IAppTenantInfo. Carries Id, Identifier, Name, ConnectionString, AdminEmail, IsActive, ValidUpto, Issuer, Plan, and QuotaLimits (a Dictionary<QuotaResource, long> of per-tenant overrides). Methods: AddValidity(months), SetValidity(DateTime) (forward-only - backdating throws), Activate() / Deactivate() (both throw for the root tenant). New tenants get a 1-month demo validity by default.
  • IAppTenantInfo - contract for tenant info; the kit references the interface where it can, allowing custom subclasses if you ever need them.
  • MultitenancyConstants - well-known values: the Root tenant (Id = "root", name, admin email), the tenant resolution identifier, and the tenant schema name.
  • ITenantInitialPasswordBuffer - singleton interface for buffering the operator-supplied tenant admin password between the create-tenant handler and the background seed step. Implementation lives in the Multitenancy module.

Identity + permissions

  • PermissionConstants - the central registry. Static Register(IEnumerable<FshPermission>) is called by each module during ConfigureServices (duplicates by Name are skipped). Properties: All, Root (where IsRoot), Admin (everything non-root), Basic (where IsBasic).
  • FshPermission(Description, Action, Resource, IsBasic = false, IsRoot = false) - immutable record. Computed Name is the canonical permission string: Permissions.{Resource}.{Action} (e.g. Permissions.Users.Create).
  • PermissionConstants.RequiredPermissionPolicyName - the name of the authorization policy that evaluates .RequirePermission() metadata. The Identity module registers the policy and sets it as both DefaultPolicy and FallbackPolicy - that double assignment matters (see gotchas).
  • SystemPermissions.All - platform permissions registered by AddHeroPlatform before any module runs.
  • RoleConstants - well-known role names (Admin, Basic) plus DefaultRoles / IsDefault(role).
  • ClaimConstants - claim type names (tenant, fullName, permission, image_url, ipAddress, exp, and the impersonation actor claims act_sub / act_tenant).
  • CustomClaims - legacy alias set covering the same core claim names.
  • ResourceConstants - framework resource names (Tenants, Users, Roles, UserRoles, RoleClaims, AuditTrails, Dashboard, Hangfire). Modules define their own resource strings in their contracts (e.g. Catalog.Products).
  • ActionConstants - action names (View, Search, Create, Update, Delete, Export, Generate, Clean, UpgradeSubscription).

Claims + authorization

  • ClaimsPrincipalExtensions - extracts user id, tenant, email, etc. from a ClaimsPrincipal. Used in middleware and handlers.
  • RequiredPermissionAttribute - implements IRequiredPermissionMetadata (a HashSet<string> RequiredPermissions). This interface is what the authorization handler keys off - it must exist exactly once, in FSH.Framework.Shared.Identity.Authorization.
  • EndpointExtensions.RequirePermission(permission, params additionalPermissions) - the canonical fluent helper for minimal-API endpoints; it attaches a RequiredPermissionAttribute as endpoint metadata. When it lists several permissions the user needs every one of them. Permissions are plain strings (modules expose them as const strings in their contracts).

Persistence shared

  • DatabaseOptions - defined here, consumed by the Persistence block. Provider (string), ConnectionString (string, validated required), MigrationsAssembly (string).
  • DbProviders.PostgreSQL + DbProviders.MSSQL - provider name constants.

Auditing markers

  • AuditAttributes - [AuditIgnore] (exclude a property from audit diffs/payloads) and [AuditSensitive(hash, redact)] (mask or hash the value when serialized). The Auditing module reads these when capturing changes.
  • HttpContextItemKeys - well-known HttpContext.Items keys building-block middleware uses to signal the audit pipeline without depending on it. Currently: QuotaRejected (set by the quota middleware on a 429).

Storage + quota DTOs

  • FileUploadRequest, PresignedUploadUrl, StoredObjectMetadata - the storage transfer types shared between the Storage block and the Files module.
  • QuotaResource - enum (ApiCalls, StorageBytes, Users, ActiveFeatureFlags). New quota dimensions get added here.

How modules consume Shared

A typical module declares string constants for endpoint wiring plus an FshPermission list for the registry - this is Catalog’s real shape:

Modules.Catalog.Contracts/Authorization/CatalogPermissions.cs
public static class CatalogPermissions
{
public static class Products
{
public const string Resource = "Catalog.Products";
public const string View = $"Permissions.{Resource}.View";
public const string Create = $"Permissions.{Resource}.Create";
// ...
}
public static IReadOnlyList<FshPermission> All { get; } =
[
new("View Products", ActionConstants.View, Products.Resource, IsBasic: true),
// ...
];
}
// CatalogModule.ConfigureServices
PermissionConstants.Register(CatalogPermissions.All);

Endpoints reference the string constants via the fluent helper:

endpoints.MapPost("/products", handler)
.RequirePermission(CatalogPermissions.Products.Create);

How to extend

Add a new resource

Add a constant to ResourceConstants, define a FshPermission set for the resource in your module’s Contracts.Authorization, register it during module startup. Done.

Add a new well-known claim

Add a name to ClaimConstants (or CustomClaims for kit-specific values). Update TokenService to issue the claim, update ClaimsPrincipalExtensions to read it. The Identity module is the place for both changes.

Add a new quota dimension

Extend QuotaResource with the new value, expose a gauge provider (see Quota), update plan config to set defaults.

Gotchas

  • PermissionConstants.Register dedupes by Name. Calling it twice with the same permission is a no-op. There is no way to remove a permission - once registered, it stays for the process lifetime.
  • IRequiredPermissionMetadata must never be duplicated. The authorization handler discovers permissions via this interface; a copy-pasted duplicate in another namespace means endpoint metadata implements the wrong interface and every .RequirePermission() gate silently stops enforcing.
  • The permission policy must stay both DefaultPolicy AND FallbackPolicy. A group-level .RequireAuthorization() attaches the default policy, which suppresses the fallback - if the default were only “authenticated”, .RequirePermission() would silently fail open (this was a real bug, fixed in PR #1290). The Identity module assigns the RequiredPermission policy to both; don’t undo that.
  • AppTenantInfo.SetValidity is forward-only. It throws “Subscription cannot be backdated” - for initial/explicit/backdated sets, assign ValidUpto directly.
  • AppTenantInfo.Plan is nullable. When null, plan resolution falls back to QuotaOptions:DefaultPlan (free by default). Set this explicitly on every tenant unless you want the default.
  • AppTenantInfo.QuotaLimits is a per-tenant override. If a resource is present in the dict, it wins over the plan’s limit. Don’t sprinkle overrides; reserve them for negotiated contracts.
  • Permissions are strings. No compile-time safety on typos. Reference the module’s constants - never write "Permissions.X.Y" literals in handlers or endpoints.

Critical files

  • src/BuildingBlocks/Shared/Multitenancy/AppTenantInfo.cs
  • src/BuildingBlocks/Shared/Identity/PermissionConstants.cs (also home of the FshPermission record)
  • src/BuildingBlocks/Shared/Identity/SystemPermissions.cs
  • src/BuildingBlocks/Shared/Identity/Authorization/RequiredPermissionAttribute.cs (and IRequiredPermissionMetadata)
  • src/BuildingBlocks/Shared/Auditing/AuditAttributes.cs
  • Core - even more foundational.
  • Web - .RequirePermission() applied to endpoints.
  • Quota - QuotaResource enum lives here, the enforcement runs there.
  • Identity module - registers IdentityPermissions.All against PermissionConstants.