# Organization

> Reference for the cluster-scoped nauthera.io/v1alpha1 Organization resource, which declares a tenant that the Nauthera operator creates or adopts on the server.

An `Organization` declares one [organization](https://www.nauthera.io/docs/v1/organizations): a tenant with its
own issuer, signing keys, users, sessions and audit log. The
[operator](https://www.nauthera.io/docs/v1/operator) creates it on the server through the gRPC
`AdminService`, or adopts the organization that already holds its slug, and keeps the
settings that may change in step with the resource.

| Property | Value |
|---|---|
| API group / version | `nauthera.io/v1alpha1` |
| Kind | `Organization` |
| Plural / short name | `organizations` / `org` |
| Scope | Cluster |
| Definition | `deploy/crds/organization-crd.yaml` in nauthera-server |

The resource is cluster-scoped because creating a tenant is not a decision one namespace
may make for the whole deployment: only someone allowed to create cluster-scoped
resources can apply one.

## Example

```yaml
apiVersion: nauthera.io/v1alpha1
kind: Organization
metadata:
  name: acme
spec:
  slug: acme
  displayName: Acme Inc.
  namespaces: [acme, grafana]        # the namespaces that may configure acme
  emailDomains: [acme.example]       # routed to acme by a broker, once verified
  emailVerificationRequired: true
  deletionPolicy: Retain
  secondFactor:
    methods: [totp, passkey]
    required: true
    rules:
      - name: passkey-off-site
        outsideZones: [office]
        methods: [passkey]
  networkZones:
    - name: office
      addressRanges:
        - 203.0.113.0/24
        - 2001:db8:1::/48
  branding:
    productName: Acme
    logoUrl: https://acme.example/logo.svg
    theme:
      color:
        accent: "#7B61FF"
```

`kubectl get organizations` shows the slug, the issuer, the phase and the `Ready`
condition's status and reason; `-o wide` adds the message.

## Before you apply one

The server decides whether the operator may manage an organization. The operator's
identity needs the `organizations` operation and the organization's slug (or `*`) in its
`server.grpc.tls.allowed_clients` entry; in the Helm chart that is
`certManager.grpcTLS.allowedClients`. The chart's
[default entry](https://www.nauthera.io/docs/v1/operator#what-the-operator-may-do-on-the-server) leaves out
`organizations` and acts in the main organization only, so add `organizations` and the
slug before applying a resource. Without them every call is
refused, and the resource reports `Refused` with `OPERATION_NOT_GRANTED` or
`OUTSIDE_GRANT`.

The main organization cannot be declared this way. A resource cannot name the slug
`default`, and the server refuses to create, change or delete the main organization over
the control plane whatever the grant says. Configure it with the deployment instead,
under `orgs.main` (see [Organizations](https://www.nauthera.io/docs/v1/organizations#the-main-org)).

## What the operator does

- **Creates or adopts.** A resource whose slug no organization holds creates one. One
  whose slug is taken adopts that organization and writes every setting that differs:
  `displayName` (the slug when unset), `fapiMode`, `emailVerificationRequired`,
  `branding`, `secondFactor` and `networkZones`.
- **Sends only what changed.** On every generation the server does not hold yet, the
  operator reads the organization and sends only the settings that differ. An unchanged
  resource produces no write.
- **Fixed at creation.** `slug`, `fipsMode` and `deletionPolicy` cannot be edited. An
  adopted organization whose FIPS mode differs from `spec.fipsMode` is reported as
  `FixedAtCreation`, and nothing is written until they agree.
- **Held to the level above.** `secondFactor` narrows the deployment's `mfa` policy and
  can never loosen it. A policy that states more than the deployment allows (a method it
  does not offer, passwordless sign-in it keeps closed, a longer `rememberForSeconds`, a
  zone it does not spare in) or a rule naming a zone the organization cannot see is
  refused, and the resource reports `Refused` naming the field and the bound. See
  [Multi-Factor Authentication](https://www.nauthera.io/docs/v1/mfa).
- **Admits its namespaces.** `namespaces` lists the namespaces whose OIDCClient,
  IdentityProvider, LDAPDirectory, Branding and BrokerMembership resources may configure
  the organization. A resource anywhere else is refused before anything reaches the
  server; see [Which organization a resource configures](https://www.nauthera.io/docs/v1/operator#which-organization-a-resource-configures).
  Adding or removing a namespace re-evaluates the resources in it.
- **Claims email domains.** Each of `emailDomains` is claimed on the server, and
  `status.emailDomains` shows the TXT record to publish under it. The operator looks a
  pending domain up every five minutes and a settled one every hour, so a record that
  disappears is noticed. A domain another organization verified is reported `Refused`;
  dropping one from the list releases its claim. A [broker](https://www.nauthera.io/docs/v1/identity-broker)
  routes an address at a verified domain to this organization.
- **Network zones** are named address ranges that the organization's rules, and its
  clients' rules, may name beside the deployment's `security.network_zones`. A zone may
  not take a deployment zone's name, and only deployment zones can spare a challenge
  (`spareZones`).
- **Default branding.** `branding` is what every sign-in page under the organization
  wears, with a client's own [Branding](https://www.nauthera.io/docs/v1/branding-resource) laid over it field
  by field. The organization's admin dashboard wears its color, typography and radius
  tokens, fonts and logo. Removing `branding` from the resource clears the default.

The operator writes the declared settings when the resource changes. A setting changed
afterwards through the admin API or `nauthera-cli update org` is not compared against
the resource, and is written over only when the resource's next generation differs from
what the server holds.

### Deleting a resource

`spec.deletionPolicy` decides, and defaults to `Retain`:

- **`Retain`** leaves the organization serving, with its users, keys and audit log.
- **`Delete`** removes the organization from the server. It must be chosen when the
  resource is created, so a later edit cannot arm a deletion. Only the organization this
  resource created or adopted is removed: one it never reached, or one recreated under the
  slug since, is left alone.

A finalizer (`nauthera.io/organization`) holds the resource until that is done. A removal
the server does not take keeps the resource in phase `Deleting` with reason
`OrganizationNotRemoved`, and is retried. Each outcome is recorded as a `Removed` or
`Retained` event.

The definition's description below says `Delete` removes the organization "with everything
it holds". The server deletes the organization's own record and stops serving it, but its
users, clients and sessions stay in the database. See [Deleting an org](https://www.nauthera.io/docs/v1/organizations#deleting-an-org).

## Spec

{/* AUTO-GENERATED-SPEC-START */}

### `spec.branding`

**Type:** `object` | **Optional**

The organization's default branding: what every sign-in page under
it wears, with a client's own Branding laid over it field by field;
what its admin dashboard wears, reduced to its color, typography and
radius tokens, fonts and logo; and what its email wears, its logo
and accent color. The same document a Branding carries. Removing it
clears the default.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `appName` | `string` | No | `—` | App name, shown as "Continue to \{appName\}". |
| `companyName` | `string` | No | `—` | Copyright holder shown in the footer. |
| `faviconUrl` | `string` | No | `—` | Icon shown in the browser tab and bookmarks: an https URL or an inline data:image/ URI. |
| `fonts` | `[]string` | No | `—` | Font stylesheet URLs to load, on fonts.googleapis.com or fonts.gstatic.com only: a stylesheet can restyle the password field, so the font CDN is the one host trusted with it. |
| `hero` | `object` | No | `—` | Hero pane content for the split layout. |
| `layout` | `string` | No | `—` | Page layout — centered (default) or split. One of: `centered`, `split`. |
| `logoUrl` | `string` | No | `—` | Logo image rendered in the header: an https URL or an inline data:image/ URI. The /ui CSP img-src blocks http images (mixed content). The admin dashboard shows an inline logo of up to 64 KiB, or one on its own origin, and nothing else. The organization's email shows its logo too; Gmail and Outlook show no SVG, so a PNG suits both. |
| `mode` | `string` | No | `—` | Default color mode. One of: `system`, `light`, `dark`. |
| `privacyUrl` | `string` | No | `—` |  |
| `productName` | `string` | No | `—` | Tenant/product name shown in the auth header. |
| `supportUrl` | `string` | No | `—` |  |
| `termsUrl` | `string` | No | `—` |  |
| `theme` | `object` | No | `—` | The design system's theme, as its defineTheme takes it. A field the design system does not know is refused; the values of its token and component maps are stored as written and checked by the server. |

### `spec.deletionPolicy`

**Type:** `string` | **Optional**

What deleting this resource does to the organization. Retain
leaves it serving, with its users, keys and audit log. Delete
removes the organization's record and its own settings, such as
its zones and email domains, and stops serving it; its users,
clients, sessions and audit log stay in the database. Immutable,
so a later edit cannot arm a deletion.

Defaults to `"Retain"`.

One of: `Retain`, `Delete`.

### `spec.displayName`

**Type:** `string` | **Optional**

Human-readable name shown in the hosted UI. Defaults to the slug.

### `spec.emailDomains`

**Type:** `[]string` | **Optional**

The email domains whose addresses a broker routes to this
organization, lower case, an internationalized name in its xn--
form. Each is claimed on the server and verified once the TXT record
status.emailDomains names is published under it; a domain another
organization verified is refused. Removing one releases its claim.

### `spec.emailVerificationRequired`

**Type:** `boolean` | **Optional**

Keeps an account whose address is not verified from signing in
until it follows an emailed link. An organization with no email
relay can send no link, so its unverified accounts stay out.

Defaults to `false`.

### `spec.fapiMode`

**Type:** `boolean` | **Optional**

Holds the organization's clients to the FAPI 2.0 Security
Profile: pushed authorization requests are mandatory and PKCE is
S256 only. It can only tighten what the deployment requires of
every organization, never relax it.

Defaults to `false`.

### `spec.fipsMode`

**Type:** `boolean` | **Optional**

Restricts the organization to the FIPS 140-3 approved algorithm
set, such as PBKDF2 instead of argon2id for passwords. A server
not running the validated module refuses to serve such an
organization rather than serve it other algorithms. Immutable.

Defaults to `false`.

### `spec.namespaces`

**Type:** `[]string` | **Optional**

The namespaces whose OIDCClient, IdentityProvider, LDAPDirectory,
Branding and BrokerMembership resources may configure this
organization. A resource in a namespace no organization lists is
refused, and one in a namespace several list names its
organization in spec.organization. Empty admits none.

### `spec.networkZones`

**Type:** `[]object` | **Optional**

The organization's own network zones, beside the deployment's: its
rules and its clients' rules may name either. A zone may not take
a deployment zone's name, and only deployment zones spare a
challenge.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `addressRanges` | `[]string` | Yes | `—` | CIDR ranges, IPv4 or IPv6; one covering every address of its family is refused. |
| `name` | `string` | Yes | `—` | A lowercase label of up to 63 letters, digits and hyphens. |

### `spec.secondFactor`

**Type:** `object` | **Optional**

The organization's second-factor policy, narrowing the
deployment's: which methods its users may use, whether holding one
is a condition of a session, whether a passkey alone signs a user
in, when a user who holds one is spared the challenge, and the rules
that narrow it further for some sign-ins. Absent is the empty
policy. The organization's admin dashboard runs under it as it is,
but is never spared; its other clients narrow it. A policy stating
more than the deployment allows is refused, and the resource reports
the refusal naming the bound.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `alwaysChallenge` | `boolean` | No | `—` | Spares no sign-in, on a remembered browser or in a zone. |
| `methods` | `[]string` | No | `—` | The methods a user may enroll and answer with. Empty inherits the level above. |
| `passwordless` | `boolean` | No | `—` | Opens sign-in with a discoverable passkey and no password. It is open only where the deployment, the organization and the client all open it. |
| `rememberForSeconds` | `integer` | No | `—` | Spares a user who holds a permitted factor the challenge on a browser that passed a second factor for them within this many seconds, while their password and factors are the ones it passed under. Zero inherits the level above's window. |
| `required` | `boolean` | No | `—` | Sends a user who holds no permitted factor through enrollment before a session is issued. |
| `rules` | `[]object` | No | `—` | Narrow this level's policy for the sign-ins that meet them. A rule only ever narrows its own level. |
| `spareZones` | `[]string` | No | `—` | Spares such a user the challenge from these of the deployment's network zones. Empty inherits the level above's. |

### `spec.slug`

**Type:** `string` | **Required**

The slug the organization is addressed at (/orgs/\{slug\}/) and
known by on the server: lowercase letters, digits and hyphens,
not starting with a hyphen. Immutable.

{/* AUTO-GENERATED-SPEC-END */}

## Status

The operator writes `status` from the server's answer. `id`, `issuer` and
`observedGeneration` come from the server; `lastReconciledTime` moves on every reconcile.
`status.issuer` is the issuer the [OIDCClient](https://www.nauthera.io/docs/v1/oidc-client),
[IdentityProvider](https://www.nauthera.io/docs/v1/identity-provider-resource), [LDAPDirectory](https://www.nauthera.io/docs/v1/ldap-directory-resource),
[Branding](https://www.nauthera.io/docs/v1/branding-resource) and
[BrokerMembership](https://www.nauthera.io/docs/v1/broker-membership-resource) resources in the namespaces it
admits are sent under. `status.emailDomains` reports each of `spec.emailDomains`: its
state (`Pending`, `Verified` or `Refused`) and the TXT record that proves it.

`phase` is one of `Pending`, `Ready`, `Error` or `Deleting`. Beside the connection
reasons every resource shares (`Refused`, `Unreachable`, `Unsupported`,
`CredentialUnavailable`, `TrustAnchorUnavailable`; see the
[OIDCClient reference](https://www.nauthera.io/docs/v1/oidc-client#status)), the `Ready` condition's reason is
one of:

| Reason | Phase | Meaning |
|---|---|---|
| `Created` | `Ready` | The server created the organization from this resource. |
| `Adopted` | `Ready` | An organization already held the slug. This resource now manages it, and any setting that differed was written. |
| `Synced` | `Ready` | The server holds the current generation of the settings. |
| `FixedAtCreation` | `Error` | The organization's FIPS mode differs from `spec.fipsMode`. Nothing is written. |
| `OrganizationNotRemoved` | `Deleting` | Under `deletionPolicy: Delete`, the server has not removed the organization yet. |

A refusal is retried every five minutes, and a server that does not answer about once a
minute.

{/* AUTO-GENERATED-STATUS-START */}

| Field | Type | Description |
|---|---|---|
| `conditions` | `[]object` | Standard Kubernetes conditions (Ready, Synced, ...). |
| `emailDomains` | `[]object` | Each domain of spec.emailDomains as the server holds its claim. |
| `id` | `string` | The server-assigned organization ID, stable for the organization's life. |
| `issuer` | `string` | The organization's OIDC issuer: the iss its tokens carry, and the issuer the resources in the namespaces it admits are sent under. |
| `lastReconciledTime` | `string` | When the operator last reconciled the resource, whatever the outcome. A stale value means the operator is not running, not the leader, or cannot see the resource. |
| `observedGeneration` | `integer` | .metadata.generation last reconciled by the server. |
| `phase` | `string` | High-level lifecycle phase. |

{/* AUTO-GENERATED-STATUS-END */}

## Related

- [Organizations](https://www.nauthera.io/docs/v1/organizations): what an organization is and how else to manage one
- [Operator](https://www.nauthera.io/docs/v1/operator): installing the operator and granting it `organizations`
- [Multi-Factor Authentication](https://www.nauthera.io/docs/v1/mfa): the second-factor policy each level narrows
- [Branding](https://www.nauthera.io/docs/v1/branding-resource): a client's own branding, laid over the default
