Add Tenants to Administration, and let an account be put in one #364

Closed
opened 2026-09-15 16:28:59 +00:00 by jcoffey-dev · 0 comments
Owner

Last of the five stacked Administration PRs. This one targets feat/admin-roles (#363). Merge order: #360#361#362#363 → this.

What it adds: Tenants under Access, gated by sysTenantQuery/sysTenantGet.

  • List: logo, storage used against its limit, and account limit.
  • Edition notice: a server that doesn't report enterprise gets a warning that anyone inside a tenant has only a normal user's permissions. It's a notice, not a gate; the section is still shown by permissions.

Panel

  • Name and logo. The logo can be https (drawn through the image proxy, since img-src is self/data/blob) or an image data URL; anything else is kept but not drawn.
  • Limits: accounts, groups, lists, domains, roles, DKIM keys, storage. Each is one quotas/<name> pointer, so the four not offered (OAuth clients, DNS servers, directories, ACME providers) keep their values. Empty means no limit.
  • Role: the most anyone inside can be allowed. Only grantable roles are offered.
  • What it holds: accounts, groups, lists, domains and roles, counted with memberTenantId queries and shown against the limits. A count that can't be read is left out, not shown as zero.
  • Domains: add a domain in no tenant, or take one out (memberTenantId on the domain). Accounts on it aren't moved.
  • Delete: offered only once every count reads zero, with type-the-name confirmation.

Accounts: a Tenant choice on create and edit, for admins who can read tenants. This is how a tenant gets its first administrator. You can't move your own account.

Mock

  • Tenant "Acme Corp" holding old-brand.example and an Administrator, plus spare.example to assign.
  • memberTenantId filters on Account, Domain, MailingList and Role queries.
  • x:Tenant get/query/set with quota validation and objectIsLinked.
  • Stalwart's rule: only an admin outside every tenant may set memberTenantId (a tenant admin gets "Cannot modify memberTenantId property").
  • Bug fixed: clearing a top-level property with null used to delete it from the working copy, and merging back kept the old value, so nothing had ever been cleared that way. Found by the domain move-out test.

Not tried live: prod has no tenants. KNOWN-ISSUES records what's read from source (including can_set_tenant in set.rs). The one assumption is that deleting a tenant that still holds something is refused; the panel avoids that case.

Translation: 39 new strings and 1 plural in all nine catalogues (1609/1625 each, 16 falling back as before).

Checked

  • Typecheck, 1284 web and 224 server tests pass, as do build and i18n:check.
  • New tests cover:
    • limit pointers, including leaving un-offered quotas alone;
    • per-kind counts, and domain moves;
    • which logos are drawn;
    • mock filters, linked delete, create/move/delete, and tenant-admin refusal;
    • panel: counts vs limits, delete blocked until empty, limit save, domain add, no domain moves without sysDomainUpdate.
  • In Chrome against the mock:
    • Acme Corp shows accounts 1 of 25, domains 1 of 2, and 2.1 GB of 50 GB, with delete blocked;
    • added spare.example (2 of 2), then took it out (1 of 2);
    • Wile's account shows Tenant: Acme Corp, and your own account's choice is locked;
    • created "Globex" (0 of 5), then deleted it through the dialog;
    • Enterprise notice shown for the mock's "oss";
    • no console errors.

Merged 2026-09-15 as coffey-labs/ihasmail@53d9cd37fb

Rebuilt from: git history, session transcript.

Last of the five stacked Administration PRs. This one targets `feat/admin-roles` (#363). Merge order: #360 → #361 → #362 → #363 → this. **What it adds:** Tenants under Access, gated by `sysTenantQuery`/`sysTenantGet`. - **List:** logo, storage used against its limit, and account limit. - **Edition notice:** a server that doesn't report `enterprise` gets a warning that anyone inside a tenant has only a normal user's permissions. It's a notice, not a gate; the section is still shown by permissions. **Panel** - **Name and logo.** The logo can be https (drawn through the image proxy, since `img-src` is self/data/blob) or an image data URL; anything else is kept but not drawn. - **Limits:** accounts, groups, lists, domains, roles, DKIM keys, storage. Each is one `quotas/<name>` pointer, so the four not offered (OAuth clients, DNS servers, directories, ACME providers) keep their values. Empty means no limit. - **Role:** the most anyone inside can be allowed. Only grantable roles are offered. - **What it holds:** accounts, groups, lists, domains and roles, counted with `memberTenantId` queries and shown against the limits. A count that can't be read is left out, not shown as zero. - **Domains:** add a domain in no tenant, or take one out (`memberTenantId` on the domain). Accounts on it aren't moved. - **Delete:** offered only once every count reads zero, with type-the-name confirmation. **Accounts:** a Tenant choice on create and edit, for admins who can read tenants. This is how a tenant gets its first administrator. You can't move your own account. **Mock** - Tenant "Acme Corp" holding `old-brand.example` and an Administrator, plus `spare.example` to assign. - `memberTenantId` filters on Account, Domain, MailingList and Role queries. - `x:Tenant` get/query/set with quota validation and `objectIsLinked`. - Stalwart's rule: only an admin outside every tenant may set `memberTenantId` (a tenant admin gets "Cannot modify memberTenantId property"). - **Bug fixed:** clearing a top-level property with `null` used to delete it from the working copy, and merging back kept the old value, so nothing had ever been cleared that way. Found by the domain move-out test. **Not tried live:** prod has no tenants. KNOWN-ISSUES records what's read from source (including `can_set_tenant` in `set.rs`). The one assumption is that deleting a tenant that still holds something is refused; the panel avoids that case. **Translation:** 39 new strings and 1 plural in all nine catalogues (1609/1625 each, 16 falling back as before). **Checked** - Typecheck, 1284 web and 224 server tests pass, as do build and i18n:check. - New tests cover: - limit pointers, including leaving un-offered quotas alone; - per-kind counts, and domain moves; - which logos are drawn; - mock filters, linked delete, create/move/delete, and tenant-admin refusal; - panel: counts vs limits, delete blocked until empty, limit save, domain add, no domain moves without `sysDomainUpdate`. - In Chrome against the mock: - Acme Corp shows accounts 1 of 25, domains 1 of 2, and 2.1 GB of 50 GB, with delete blocked; - added `spare.example` (2 of 2), then took it out (1 of 2); - Wile's account shows Tenant: Acme Corp, and your own account's choice is locked; - created "Globex" (0 of 5), then deleted it through the dialog; - Enterprise notice shown for the mock's "oss"; - no console errors. **Merged** 2026-09-15 as coffey-labs/ihasmail@53d9cd37fbba <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.