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. 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.
  • QuotaQuotaResource enum lives here, the enforcement runs there.
  • Identity module — registers IdentityPermissions.All against PermissionConstants.