Skip to content

Feature: tenant-local human identities for app-per-tenant isolation #99

Description

@arvindh123

At a glance

Introduce tenant-local human identities for the app-per-tenant model. A person who signs up in App A must not automatically exist in App B: the same email may create separate entities in each tenant, with independent credentials, sessions, verification, password recovery, roles, and lifecycle. All work should land through small PRs targeting the integration branch feature/tenant-local-identities, followed by one stabilization PR from that branch to main.

Context and product decision

Atomic is adopting this application boundary:

  • One Atomic app maps to exactly one Atom tenant.
  • Atomic/app users remain Atom human entities; a tenant is never a user.
  • Finished-app users require complete tenant identity isolation.
  • Platform administrators and service identities remain separate from app users.

Atom currently supports globally reusable identities. In that model, a user who signs up once can later receive access to another tenant without creating a separate app identity. That is not the desired behavior for app-per-tenant isolation.

Problem statement

For tenant-local apps, signing up in one app must not establish an identity or reusable credential in any other app.

Given the same email address in two app tenants, Atom must represent two independent identities:

Concern App A / Tenant A App B / Tenant B
Entity Independent entity ID Independent entity ID
Password and credentials Independent records Independent records
Verification and recovery Tenant A only Tenant B only
Sessions and JWTs Tenant A only Tenant B only
Roles and policies Tenant A only Tenant B only
Lifecycle Tenant A only Tenant B only

The user must separately sign up or accept an invitation in App B. Passwords may coincidentally have the same value if chosen by the user, but they are stored and managed independently; resetting App A's password must not affect App B.

Goals

  • Isolate app-user identities by tenant.
  • Require trusted tenant/app context for signup, login, verification, recovery, invitation acceptance, and OAuth/OIDC linking.
  • Allow the same normalized email in different tenants while preventing duplicates inside one tenant.
  • Bind credentials, sessions, JWTs, roles, and lifecycle operations to the identity's home tenant.
  • Reject cross-tenant memberships, role assignments, policies, and credential use for tenant-local identities.
  • Preserve platform/global identities for Atom administrators and system services.
  • Provide a safe, observable, retryable migration path for existing deployments.

Non-goals

  • Cross-app single sign-on.
  • Automatically sharing one user account or session across tenants.
  • Copying API keys, access tokens, certificates, or active sessions during migration.
  • Treating Atomic collaborators as finished-app users.
  • Adding tenant_user as an EntityKind; human remains the principal kind, while identity scope describes where the identity exists.
  • Changing the online authorization principle: tokens identify identity/session; live permissions remain database-backed.

Proposed domain model

Identity scope

Introduce an explicit identity scope with two exhaustive values:

  • platform: no home tenant; reserved for platform administration and approved system/service identities.
  • tenant: a home tenant is mandatory; used for isolated app users.

EntityKind remains responsible for what the principal is (human, service, and so on). Identity scope is responsible for where that identity exists.

Tenant identity mode

Support an explicit tenant identity mode for controlled rollout:

  • global: legacy behavior for existing tenants.
  • tenant_local: isolated identities for app tenants.

All newly provisioned Atomic app tenants should use tenant_local. Existing tenants should not silently change behavior during migration.

Identity uniqueness

  • Tenant-local humans are unique by (tenant_id, normalized_email) among live identities.
  • Platform humans retain global normalized-email uniqueness.
  • A tenant-local identity must always have a home tenant.
  • A tenant-local identity cannot be moved to or reused by another tenant.

Functional requirements

Signup and open enrollment

  • Signup must resolve a trusted tenant ID or alias before identity lookup.
  • The target tenant must be active, not deleted, and configured for open enrollment.
  • Email lookup and conflict handling must occur only inside the target tenant.
  • Entity creation, password credential creation, verification state, tenant membership, and default Member role assignment must be atomic.
  • The default role must come from server-side tenant enrollment configuration; clients must not supply an arbitrary role ID.
  • Repeated requests must be idempotent and must not create duplicate live identities.
  • Public responses must not disclose identities from other tenants.

Login

  • Tenant-local login must require tenant ID or alias.
  • Identity lookup must be restricted to that tenant and identity scope.
  • App A credentials must fail when presented to App B, even when the email and password text are identical.
  • Login must fail when the tenant or entity is inactive, frozen, deleted, suspended, or otherwise ineligible.
  • Successful sessions must record the home tenant.
  • JWTs must carry the authenticated tenant context and be rejected when used against another tenant.

Email verification and password recovery

  • Verification and recovery tokens must be bound to both entity and tenant.
  • Tenant context must be resolved from trusted token state or validated app configuration, not accepted blindly from a browser field.
  • Resetting or verifying one tenant identity must not affect an identity with the same email in another tenant.
  • Responses must resist account and cross-tenant enumeration.
  • Redirects and email content must return the user to the correct application.

Invitations

  • Invitations belong to exactly one tenant.
  • Acceptance must create or activate a tenant-local identity only in the invitation tenant.
  • An identity with the same email in another tenant must not be linked or reused.
  • Invitation role assignment must be limited to roles belonging to the invitation tenant.
  • Revocation, expiration, repeated acceptance, suspension, and removal must remain tenant-local and auditable.

OAuth/OIDC

  • External identity links must be tenant-scoped.
  • Provider identity uniqueness must include tenant, issuer/provider, and provider subject.
  • The same external account may create separate local identities in separate app tenants.
  • OAuth callbacks must cryptographically bind and validate the intended tenant/application.

Credentials, sessions, and tokens

  • Every credential belongs to one tenant-local entity and inherits its home-tenant boundary.
  • Sessions record and enforce the home tenant.
  • Access-token and shared-key verification must reject tenant mismatch.
  • API keys, access tokens, certificates, and sessions must never be implicitly reusable in another tenant.
  • Revocation, rotation, replacement, listing, and reveal operations must enforce the same boundary.

Authorization and administration

  • Tenant-local identities can receive tenant memberships, role assignments, direct policies, and group memberships only within their home tenant.
  • Assignment-time guardrails must reject cross-tenant subject/object/role combinations.
  • Client-provided tenant IDs must always be checked against authenticated identity context.
  • Platform administrators may manage tenant-local identities through platform-authorized control-plane operations, but a platform identity must not silently become an app user.
  • Tenant admins must not enumerate, read, mutate, or recover identities from sibling tenants.

Tenant lifecycle

  • Tenant suspension/freeze must block tenant-local authentication and authorization according to existing tenant lifecycle semantics.
  • Tenant deletion must revoke affected sessions and credentials as required by Atom's deletion rules.
  • Restore must follow existing credential/session/certificate restoration rules without reviving later manual revocations.
  • Purge must physically remove tenant-local identities and clean all authorization references through the canonical purge paths.

APIs, bootstrap, and events

  • GraphQL, REST/auth, and applicable gRPC surfaces must use the same tenant-qualified semantics.
  • Bootstrap must represent identity scope and tenant identity mode without introducing reusable production secrets.
  • Audit records and domain events must include sufficient tenant/entity context for investigation without leaking private credential material.
  • Mutation success events must remain transactional with their mutations according to the existing outbox rules.
  • Error responses should use stable, safe error categories and correlation metadata where supported.

Security invariants

The implementation is not complete unless all of these hold:

  1. A tenant-local entity always has exactly one home tenant.
  2. The same entity ID is never used as an app user in multiple tenants.
  3. Tenant A credentials, sessions, JWTs, verification tokens, reset tokens, OAuth state, and invitations fail in Tenant B.
  4. Cross-tenant role assignments, policies, memberships, and credential administration fail closed.
  5. Tenant aliases, UUIDs, role IDs, entity IDs, redirect values, and request attributes cannot be tampered with to escape the home tenant.
  6. Authentication responses do not reveal whether the same email exists in another tenant.
  7. Platform identities and tenant-local identities cannot be confused by lookup fallback.
  8. Online permission checks continue to hit the database; identity scoping does not move permissions into JWTs.

Backward compatibility and migration

Use an additive, staged rollout:

  1. Add identity scope and tenant identity mode while preserving existing behavior.
  2. Classify/backfill existing identities deterministically and report ambiguous cases.
  3. Keep existing tenants in legacy/global mode until explicitly migrated.
  4. Provision all new Atomic app tenants in tenant-local mode.
  5. For an existing app migration, create a distinct tenant-local entity per existing app member.
  6. Revoke old sessions at cutover.
  7. Prefer tenant-specific password setup/reset instead of silently sharing credentials.
  8. Do not copy API keys, access tokens, certificates, or active sessions.
  9. Provide dry-run reporting, conflict detection, resumability, and an auditable migration result.
  10. Define rollback boundaries before enabling tenant-local authentication for a migrated tenant.

Any decision to copy password hashes into independent credential records for migration must receive a separate security review. Even if copied initially, the records must be independent afterward.

Delivery and branch strategy

Create the integration branch:

feature/tenant-local-identities

Delivery rules:

  • Create each ticket branch from the latest integration branch.
  • Send every feature PR to feature/tenant-local-identities, not directly to main.
  • Keep schema changes additive until all runtime readers support the new model.
  • Require tests, migration notes, and API compatibility notes in every PR.
  • Do not merge incomplete authorization paths behind a default-on flag.
  • After all tickets and end-to-end tests pass, open one stabilization PR from feature/tenant-local-identities to main.

Atomic/Phoenix integration belongs in the separate Atomic repository and should use its own integration branch, for example feature/app-per-tenant. Branches and PRs cannot span repositories.

Proposed ticket and PR breakdown

ATOM-TLI-001 — Architecture decision and contracts

  • Record app-per-tenant and tenant-local identity decisions.
  • Finalize terminology, invariants, compatibility policy, and API behavior.
  • Decide legacy/global tenant behavior and migration boundaries.

ATOM-TLI-002 — Schema and typed domain model

  • Add identity scope and tenant identity mode.
  • Add tenant-local email/provider uniqueness constraints.
  • Add required indexes and consistency constraints.
  • Add typed Rust enums with exhaustive matching.
  • Keep migration additive and safe for existing data.

ATOM-TLI-003 — Tenant-qualified repository and service primitives

  • Introduce canonical tenant-qualified identity lookup.
  • Remove unsafe global-email fallback from tenant-local flows.
  • Add reusable consistency validation for entity, tenant, and scope.
  • Cover duplicate, deleted, inactive, and ambiguous cases.

ATOM-TLI-004 — Signup, verification, and default enrollment

  • Implement tenant-local signup.
  • Create membership and default role atomically.
  • Bind verification state and redirects to the tenant.
  • Add idempotency and enumeration-resistant behavior.

ATOM-TLI-005 — Login, sessions, and JWT tenant binding

  • Require tenant qualification for tenant-local login.
  • Bind sessions and JWT identity context to the home tenant.
  • Reject tenant mismatch across all authentication paths.
  • Preserve platform login through an explicit, separate path.

ATOM-TLI-006 — Recovery and credential lifecycle

  • Tenant-bind password reset and credential management.
  • Enforce scope in create, replace, revoke, list, reveal, and verification operations.
  • Verify independent password changes across tenants.

ATOM-TLI-007 — Invitations and tenant-local membership lifecycle

  • Create/activate only identities local to the invitation tenant.
  • Enforce local role selection, acceptance, expiration, revocation, suspension, and removal.
  • Add transactional events and audit coverage.

ATOM-TLI-008 — OAuth/OIDC tenant-local linking

  • Scope external identities and callbacks to tenants.
  • Prevent provider-account reuse across app tenants.
  • Add tamper-resistant callback/state tests.

ATOM-TLI-009 — Authorization and assignment guardrails

  • Reject cross-tenant memberships, roles, direct policies, group membership, and credential administration.
  • Apply checks consistently across GraphQL, gRPC, internal services, and bootstrap.
  • Verify single-connection transaction safety for guardrail queries.

ATOM-TLI-010 — Tenant lifecycle, restore, and purge

  • Apply suspend/delete/restore/purge semantics to tenant-local identities.
  • Preserve credential revocation provenance and certificate rules.
  • Verify canonical authorization-reference cleanup.

ATOM-TLI-011 — Existing-user migration tooling

  • Add dry-run planning, conflict reports, resumability, idempotency, and audit output.
  • Materialize independent identities per target app tenant.
  • Revoke or replace unsafe shared credentials and sessions.

ATOM-TLI-012 — Public contracts, bootstrap, and documentation

  • Update GraphQL, REST/auth, applicable gRPC, bootstrap, examples, and operator guidance.
  • Document tenant-qualified login/signup/reset behavior and errors.
  • Document migration and rollback procedures.

ATOM-TLI-013 — Adversarial and end-to-end validation

  • Same-email/different-tenant creation tests.
  • Cross-tenant password, credential, JWT, session, invitation, verification, reset, OAuth, role, and policy tampering tests.
  • Tenant lifecycle and migration tests.
  • App-per-tenant portability test against a fresh Atom/Postgres environment without Atomic/Phoenix in the runtime path.

Acceptance criteria

  • New app tenants can be configured for tenant-local identities.
  • The same normalized email can create separate live identities in two different tenant-local tenants.
  • Duplicate live identities inside one tenant are rejected.
  • Tenant-local signup and login require trusted tenant context.
  • App A credentials and sessions are rejected by App B.
  • Verification, recovery, invitations, and OAuth are tenant-bound.
  • Tenant-local identities cannot receive cross-tenant access assignments.
  • Password and lifecycle changes in one tenant do not affect another tenant's identity.
  • Platform identities remain supported through explicit platform flows.
  • Existing tenants retain compatible behavior until explicitly migrated.
  • Migration is observable, idempotent, resumable, and documented.
  • Required audit/outbox invariants remain intact.
  • Unit, integration, adversarial, migration, and portability tests pass.
  • cargo test, cargo clippy -- -D warnings, and cargo fmt --check pass before the integration branch is proposed for main.

Open decisions to resolve in ATOM-TLI-001

  • Should identity scope be stored explicitly or derived from tenant ownership with a database invariant?
  • Should tenant identity mode be tenant configuration in the database, deployment configuration, or both?
  • What is the explicit platform-login surface after tenant qualification becomes mandatory?
  • How should legacy global users be migrated: tenant-specific password setup, reviewed password-hash duplication, or another controlled flow?
  • Which existing non-human global entities may intentionally operate across tenants, and through which explicit platform policies?
  • How should tenant-specific email templates, redirects, and OAuth provider configuration be selected and validated?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions