Changelog - 2026-05-27
Casbin Domain Matching Function Support
New FeatureIn one line. CasbinAuthorizationEnforcer can now match wildcard or pattern domains in role-grouping policies, so one policy line can grant a role across every tenant instead of one line per tenant.
What changed
- New
domainMatchingenforcer option. Opt-in{ roleDefinition, fn }config registers a Casbin domain matching function (keyMatch,keyMatch2,keyMatch3,keyMatch4, orregexMatch) on a role definition, so a storedg, User_x, Role_y, *matches a request in any domain, whileg, User_x, Role_y, Merchant_Xstill matches onlyMerchant_X. - New
CasbinDomainMatchingFunctionsconstant class. Exposes the five supported functions plusSCHEME_SET,isValid(), and theTCasbinDomainMatchingFunctiontype. - Fail-loud validation.
configure()throws if the configuredroleDefinitionis not declared in the Casbin model, instead of silently no-opping - which would leave global wildcards permanently unmatched with no signal. - Survives reloads. The matching function is registered once on the enforcer's role manager, so it persists across every
loadFilteredPolicyreload.
Who is affected
- Multi-tenant apps using Casbin RBAC with domains. No action needed - the option is off by default and domains keep comparing as exact strings unless you opt in.
- Apps where a user holds a role across many tenants. Opting in collapses that user's materialized policy line count from
permissions x domainstomemberships + permissions, since role permissions can now be written domain-agnostic (p.dom = "*").
Details
Enable it on the enforcer registration:
import {
AuthorizationEnforcerRegistry,
CasbinDomainMatchingFunctions,
CasbinEnforcerModelDrivers,
type ICasbinEnforcerOptions,
} from '@venizia/ignis';
AuthorizationEnforcerRegistry.getInstance().register({
container: this,
enforcers: [
{
enforcer: CasbinAuthorizationEnforcer,
name: 'casbin',
type: AuthorizationEnforcerTypes.CASBIN,
options: {
model: { driver: CasbinEnforcerModelDrivers.TEXT, definition: CASBIN_RBAC_MODEL },
adapter,
cached,
normalizePayloadFn,
// Register keyMatch on the `g` role definition so wildcard domains work:
domainMatching: { roleDefinition: 'g', fn: CasbinDomainMatchingFunctions.KEY_MATCH },
} satisfies ICasbinEnforcerOptions,
},
],
});This requires a Casbin model whose matcher invokes g with the request domain and calls the matching function against it, for example:
[matchers]
m = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.actAvailable matching functions
| Function | Value | Notes |
|---|---|---|
CasbinDomainMatchingFunctions.KEY_MATCH | 'keyMatch' | * is the only wildcard; exact compare otherwise. Recommended for Merchant_<uuid>-style domains |
CasbinDomainMatchingFunctions.KEY_MATCH_2 | 'keyMatch2' | Adds URL-path :param segment matching |
CasbinDomainMatchingFunctions.KEY_MATCH_3 | 'keyMatch3' | Adds {param} segment matching |
CasbinDomainMatchingFunctions.KEY_MATCH_4 | 'keyMatch4' | {param} with repeated-name equality checks |
CasbinDomainMatchingFunctions.REGEX_MATCH | 'regexMatch' | Treats the stored/policy value as a full regular expression |
NOTE
The matching function is applied as fn(requestDomain, policyDomain) - the wildcard must live on the stored/policy side. keyMatch("Merchant_X", "*") is true; keyMatch("Merchant_X", "Merchant_Y") is false.
WARNING
When using a domain model (r = sub, dom, obj, act), normalizePayloadFn must always return a domain. If it returns undefined, the enforcer falls back to the 3-argument enforceSync form, which mis-evaluates against a 4-argument model.
| File | Package |
|---|---|
src/components/auth/authorize/common/constants.ts | core |
src/components/auth/authorize/common/types.ts | core |
src/components/auth/authorize/enforcers/casbin.enforcer.ts | core |