Skip to main content
Wildo.ai Coming soon

Authorization and isolation

Put information in the scope it belongs to

Some information belongs to the application, some to a customer organization, some to one person and some to a visitor who has not registered. Wildo makes that ownership an explicit part of the resource definition.

Four separate cards represent application, customer, personal and visitor scopes.

Put information in the scope it belongs to

Some information belongs to the application, some to a customer organization, some to one person and some to a visitor who has not registered. Wildo makes that ownership an explicit part of the resource definition.

Those distinctions let shared settings, private preferences and tenant business records coexist without treating them as the same kind of data.

Example — Keep personal preferences beside team records

A person’s preferences belong to that person. The projects they work on belong to their organization, and the application’s configuration belongs to the deployment.

For engineers

ResourcePrimaryScope names the resolved scope. Application authors declare ownership through relationships; the resource factory derives resourcePrimaryScope from that declaration. Scope-root resources anchor themselves, and polymorphic resources receive the relationship for their selected scope variant. Operation roles then decide who can act within the resolved scope.

ScopeOwnershipTypical use
APPLICATIONThis application deploymentShared configuration
ORGANIZATIONSOne customer organizationBusiness records
USER_SELFOne authenticated personPersonal preferences
ANONYMOUSOne visitor sessionWork started before registration

Declare the ownership that determines the scope

Wonder Todos registers this relationship alongside its resource definitions. The parent is an organization, the child is a todo, and isPrimaryScope identifies the ownership relationship used to derive the child’s scope. Source: tasks-manager.relationships.ts; source comments omitted.

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

With this registered relationship, the factory resolves organization ownership and its foreign-key field. Another relationship, such as the todo’s assignee, remains a reference; it does not replace the primary owner. An ordinary resource without a resolvable primary scope is rejected during configuration instead of silently receiving a broader scope.

Let a draft belong to a person or a visitor

Wonder Todos’ draft notes demonstrate USER_SELF and ANONYMOUS without changing the business object. The schema declares nullable userId and anonymousUserId foreign keys, both excluded from ordinary updates. The resource opts into anonymous ownership with isAnonymizable: true and chooses transpositionPolicy: ResourceTranspositionPolicy.ADD for the later account handoff.

Its authored relationship starts from the signed-in person. This is the relevant declaration from tasks-manager.relationships.ts, including the child lifecycle decision:

createResourcesRelationship(
  CoreResourceType.USERS, TasksManager_ResourceType.DRAFT_NOTES,
  ResourceRelationshipCardinality.ONE, ResourceRelationshipCardinality.MANY,
  {
    nature: RelationshipNature.COMPOSITION,
    isPrimaryScope: true,
    foreignKeyField: 'userId',
    parentResourceRequirement: ResourceParentResourceRequirement.OPTIONAL,
    accessScopeStrategy: ResourceRelationshipAccessScopeStrategy.REQUIRES_CONTEXT,
    childOperations: { lifecycle: { onParentDelete: { enabled: true, mode: ChildLifecycleMode.IMMEDIATE } } },
    contextPolicy: {},
  }
)

The relationship helper and enum vocabularies come from @wildo-ai/saas-models; TasksManager_ResourceType is application-owned. OPTIONAL accommodates a visitor-owned row without a userId; it does not make another person’s records public. The anonymizable resource factory supplies the anonymous ownership relationship. Application authors do not hand-author a second unrelated draft resource.

The draftNotes_ResourceConfiguration_InitializationFactory is registered in the tasks-manager resource factory map, and the shared module contributes that map together with its relationships and field identifiers. Its ordinary create/read/list/update/delete API variants accept APP_USER and APP_ANONYMOUS; ownership confinement still applies to each caller. Allowing both roles does not merge their records.

Observe the two ownership paths

With BACKEND_URL including the API prefix, an authenticated person lists their drafts using their own user ID and Bearer session:

curl --fail-with-body "$BACKEND_URL/users/$USER_ID/draft-notes" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN"

For an already established anonymous session, use its UUID in the path and its signed session token in the header. The UUID is an identifier, not a credential:

curl --fail-with-body "$BACKEND_URL/anonymous-users/$ANONYMOUS_UUID/draft-notes" \
  -H "x-anonymous-session: $ANONYMOUS_SESSION_TOKEN"

The standard client establishes and carries that session; anonymous sessions and transposition explains its lifecycle. Supply the application’s frontend-service header where its deployment requires it. Neither call turns a user-supplied owner field into authority.

Verify isolation using existing disposable drafts: each owner can read their own record; substituting another user or visitor’s path/record must not expose or mutate that record. Check the allowed read first so an unavailable route cannot masquerade as successful isolation. The existing scope-isolation-user-anon-hostile.e2e.ts exercises these separate ownership paths and re-reads storage after denied mutations.

Carry the scope through the operation

The framework resolves the scope root and contextual identifier, builds the caller’s execution context and uses the matching authorization path. A user-scoped resource is not made accessible to everyone merely because it sits in the same application database.

Organization units narrow authority inside an organization; they do not introduce a fifth primary scope. Likewise, a directory of users is an application administration surface, not the same interaction as reading one person’s own preferences.

Plan the visitor-to-account transition

Anonymous ownership can support work before registration. A resource’s transposition policy determines whether that work is reassigned, replaces existing work or is discarded when ownership moves. Treat that transition as a separate product decision from who may operate on the visitor’s data now.

See tenant confinement for organization records and anonymous sessions for the registration handoff.

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.