> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# identity

> identity is how the platform knows who is asking and whether they may. It holds our customers' staff, the roles they carry, the machines that act on their behalf.

> `identity` is how the platform knows who is asking and whether they may. It holds our customers'
> staff, the roles they carry, the machines that act on their behalf, and the OAuth2 provider that
> lets a third party integrate without ever handling a password. Every endpoint in the suite is
> gated by something defined here.

## Who this is for

| Persona                                               | Hires this module to                                                                                                                           |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Tenant administrator**                              | Invite their team, decide who can see what, and be sure a marketing assistant cannot issue points or read a national ID.                       |
| **Developer** (tenant or integrator)                  | Get a scoped API key in a minute, or build an integration that asks the tenant's permission through OAuth2 rather than asking for credentials. |
| **Softcrum operator**                                 | Help a tenant who is stuck, without becoming an unaccountable superuser.                                                                       |
| **Security reviewer** (the tenant's, or an auditor's) | Answer "who could have done this, and who did" from a trail rather than from assurances.                                                       |

## The problem today

Access control is the part of a B2B platform that customers do not ask about until it fails, and
then it is the only thing they ask about. Three specific failures are worth naming because the
design exists to prevent them.

**Roles that are really one switch.** Many platforms ship "admin" and "member" and stop. The moment
a tenant has a marketing agency, a call centre and a finance team touching the same account, that
model forces them to over-grant. Our permission model is per action from the start (DEC-D5), so
"can read contacts but not their national IDs" is expressible rather than aspirational.

**Integrations that ask for a password.** Without an OAuth2 provider, a third party integrating
with us asks the tenant for an API key with everything on it, or worse, for console credentials.
Being an OAuth2 provider is what makes an integration ecosystem possible without that.

**Support access nobody can audit.** Operators need to see what a tenant sees. Done casually, that
is an undetectable read of every customer's data. Time-boxed, reason-required, audit-logged and
**visible to the tenant** turns it from a liability into a trust signal.

## What it does *(normative)*

* Holds **organizations and users**, where a user's email is unique **per organization**. The same
  person working with two of our customers holds two independent accounts that cannot be correlated
  — isolation over convenience (`standards/data.md` §2b).
* **Invitations** with expiring single-use tokens, retained after acceptance as evidence of who let
  whom in.
* A **global permission registry** of `{module}.{resource}.{action}`, generated from the route
  declarations themselves so it cannot drift from what the code actually enforces.
* **Roles** that bundle permissions, seeded as system roles and extensible by the tenant.
* **Machines carry roles exactly like users** (DEC-D5): an API key or an OAuth client is a subject
  with role assignments, checked by the same code path.
* **Authentication**: password, magic link and passkey, with social and enterprise SSO in F1b.
* **MFA** with TOTP and hashed single-use recovery codes.
* **Sessions** a user can inspect and revoke per device.
* **OAuth2 in both directions**: as a provider, so a partner tool can act on a tenant's behalf and a
  tenant's site can offer "Sign in with Softcrum"; and as a client, so a tenant's staff can sign in
  through their own identity provider. The tenant chooses; both can run at once.
* **Support impersonation**, time-boxed, reason-required, and visible in the tenant's own audit trail.

## Non-goals *(normative)*

* **Not the member realm.** Members are authenticated contacts and live in `core` (ADR-010).
  A token from one realm never satisfies the other.
* **Not a public identity network.** "Sign in with Softcrum" serves a tenant's own surfaces and the
  partners they authorise. A client is always scoped to the organization that granted it, and a
  member subject is scoped to that organization too.
* **Not SCIM or directory sync in F1.** Enterprise provisioning is a real ask that arrives with real
  enterprise customers; the membership model is shaped so it can be added without migration.
* **Not per-record permissions.** Permissions are per action, not per row. "Can edit this specific
  campaign but not that one" is deliberately out — it is where authorization models become
  unreasonable, for us and for the administrator configuring them.
* **No password for members.** That is a `core` decision (FS-CORE-0009) and it stands: no member
  password means no member password to leak.

## Success

| Measure                                             | Target                                                   | By                    |
| --------------------------------------------------- | -------------------------------------------------------- | --------------------- |
| Time from signup to an invited teammate working     | under 5 minutes, unaided                                 | G1-Engage             |
| Endpoints with exactly one declared permission      | 100%, enforced by CI                                     | G1-Engage             |
| Permission registry drift between code and database | zero, enforced by CI                                     | G1-Engage             |
| Auth p95 (sign-in, token exchange, key validation)  | \<300 ms                                                 | certification, pre-G1 |
| Impersonation sessions with no reason recorded      | zero, by construction                                    | always                |
| MFA adoption among tenant administrators            | required, not measured — it is mandatory for admin roles | G1-Engage             |

## Commercial shape

`identity` is not sold and is not metered. Two capabilities are plan-gated rather than priced:
**enterprise SSO**, which is the standard enterprise-tier feature across the industry, and
**tenant-defined custom roles**, where lower plans get the seeded system roles.

Seats are not a billing dimension. The suite bills marketable contacts and volume (ADR-014,
ADR-016), and adding a per-seat charge on top would punish a tenant for involving more of their own
team — which is the opposite of what drives adoption.

## Phases

| Phase   | Contents                                                                                                                                     | Target                |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| **F1a** | Organizations, users, memberships, invitations · permission registry and RBAC · password, magic link and passkey · sessions · MFA · API keys | G1-Engage, 2026-11-01 |
| **F1b** | OAuth2 provider with consent · social login · enterprise SSO · support impersonation                                                         | post-G1               |
| **F2**  | SCIM provisioning · fine-grained delegation · security policy per organization (session lifetime, MFA enforcement, IP allowlist)             | 2027                  |

## Compliance and risk

For the data in this module Softcrum is the **controller**, not the processor — these are our own
users, not our customers' customers (DEC-J1). That is why it is a separate schema from `core`
(ADR-022), and it means our own privacy policy governs here while the DPA governs there.

Three risks shape the design:

* **Privilege escalation across realms.** Mitigated structurally: separate realms, separate token
  audiences, an OAuth client that declares one realm and can never reach the other, and a test
  asserting a member token is rejected by every console endpoint.
* **Cross-tenant identity leakage.** The same email in two organizations must never be correlatable.
  Mitigated by per-tenant uniqueness, per-organization `sub` claims, and invitation responses whose
  wording and timing do not reveal that an address is known elsewhere.
* **A leaked API key.** Mitigated by hashing at rest, a visible prefix so a leaked key can be
  identified without revealing it, `last_used_at` for detection, and rotation without downtime.
* **An operator reading customer data undetected.** Mitigated by making impersonation impossible
  without a reason and a time box, and by surfacing it in the tenant's own audit trail — the tenant
  is the one with the incentive to notice.

## Dependencies

Better Auth for both realms (ADR-010) — the only vendor in this module, and the one whose
replacement cost the ADR-022 alternatives section weighs explicitly. Upstash for rate limiting and
replay checks. Resend for magic links and invitations. `core.audit_log` for the trail.

## Open questions

| # | Question                                                                                                         | Decides | By                          |
| - | ---------------------------------------------------------------------------------------------------------------- | ------- | --------------------------- |
| 1 | Is MFA mandatory for every user, or only for roles holding sensitive permissions?                                | Daniel  | before FS-IDN-0005 approval |
| 2 | Default session lifetime for the console — 12 hours, or 30 days sliding with MFA re-prompt on sensitive actions? | Daniel  | before FS-IDN-0004 approval |
| 3 | Do we ship passkeys in F1a, or is password plus magic link enough to start?                                      | Daniel  | before FS-IDN-0003 approval |

## Changelog

| Version | Date       | Change        | Why | Author                 |
| ------- | ---------- | ------------- | --- | ---------------------- |
| 0.1.0   | 2026-08-17 | Initial draft | —   | daniel + claude-opus-5 |
