Skip to main content
Wildo.ai Coming soon

Authorization and isolation

Keep each customer’s records in their account

Organization-owned records are confined through the framework’s resource and repository paths. Application code can work with the current organization’s data without rebuilding a tenant filter in every endpoint.

A request reaches Acme's records while Northwind's records remain separate.

Keep each customer’s records in their account

Organization-owned records are confined through the framework’s resource and repository paths. Application code can work with the current organization’s data without rebuilding a tenant filter in every endpoint.

The organization comes from the authorized request context, while shared confinement rules serve both MongoDB and PostgreSQL.

Example — Two customers use the same project feature

Both organizations can list and edit projects through the same application code. Each request operates within its authorized organization, so one customer’s records do not become the other’s results.

For engineers

Register the primary ownership relationship with the application’s resource relationships. The resource factory derives organization scope from it; resourcePrimaryScope is not a second author-maintained setting. In Wonder Todos, tasks-manager.relationships.ts contains this declaration (source comments omitted):

createResourcesRelationship(
  CoreResourceType.ORGANIZATIONS, TasksManager_ResourceType.TODOS,
  ResourceRelationshipCardinality.ONE, ResourceRelationshipCardinality.MANY,
  {
    nature: RelationshipNature.COMPOSITION,
    isPrimaryScope: true,
    foreignKeyField: 'organizationId',
    contextPolicy: {}
  }
)

The todo schema supplies the organizationId foreign-key field. The relationship connects that field to the organization parent and makes it the primary ownership axis. Register it before initializing the resource configuration, alongside the resource’s operations and other relationships.

When an authorized caller lists todos through the organization’s contextual resource operation, the framework resolves that organization in the execution context and carries confinement into the repository query. The same declaration serves both persistence adapters. Application code still supplies the operation’s roles and business filters; it does not infer tenant authority from a submitted organization ID.

The shared confinement function checks the authenticated organization and the operation’s declared crossing policy before adding the top-level ownership predicate:

This implementation excerpt from initiator-organization-confinement.backend.ts shows the decision in context; explanatory source comments are omitted.

export function assignInitiatorOrganizationConfinementToFilter<TFilter extends object>(params: {
  readonly filter: TFilter;
  readonly executionContext: InitiatorOrganizationConfinementSignal;
  readonly blacklistedFields: ReadonlySet<string>;
  readonly mainSchema: unknown;
  readonly schemaPaths: InitiatorOrganizationConfinementPathResolver;
  readonly isFieldPersisted: (fieldName: string) => boolean;
}): boolean {
  const initiatorOrganizationId = params.executionContext.initiatorIds?.organizationId;
  if (!initiatorOrganizationId) {
    return false;
  }

  if (params.executionContext.operation?.admitsCrossTenantPlatformAdministration === true) {
    return false;
  }

  if (
    params.blacklistedFields.has(INITIATOR_ORGANIZATION_CONFINEMENT_FIELD)
    || !params.isFieldPersisted(INITIATOR_ORGANIZATION_CONFINEMENT_FIELD)
  ) {
    return false;
  }

  const organizationPaths = params.schemaPaths.findNestedFieldPathsFromZodSchema(
    params.mainSchema,
    INITIATOR_ORGANIZATION_CONFINEMENT_FIELD,
  ).filter((fullPath) => !fullPath.includes('.'));

  let applied = false;
  organizationPaths.forEach((fullPath) => {
    if (!params.schemaPaths.hasNestedPath(params.filter, fullPath)) {
      params.schemaPaths.setNestedValue(params.filter, fullPath, initiatorOrganizationId);
      applied = true;
    }
  });

  return applied;
}

Follow authority, rather than trusting a payload

The execution-context creator obtains memberships and removes authority from organizations that are no longer operational. The normal create path supplies contextual ownership fields; client data is not the authority for which tenant owns the new record.

Both persistence adapters call the shared confinement authority. A nested field that happens to be named organizationId is ordinary data unless it is the declared top-level ownership axis; filtering every matching field name would hide legitimate records.

Use the declared exceptional path

A deliberately admitted cross-tenant platform operation can step outside normal confinement. That is an operation-level declaration with its own admission requirements, not a global super-administrator shortcut. Arbitrary direct database access is outside the resource path and must not be mistaken for an authorized resource operation.

Organization listings need an additional rule because the organization is the scope root itself: see account-list confinement.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.