Oliver Kieselbach
86f8fd5ace
feat(delegated): tenant templates — app-internal tenant bundles for MSP delegated admins
...
Let a GlobalAdmin define a named Tenant Template (a bundle of tenants) and
assign a delegated-admin UPN to the template instead of to each tenant. A UPN's
effective scope = union of direct grants + every tenant in every assigned
template, resolved in DelegatedAdminService.GetScopeAsync — so middleware
enforcement, /auth/me delegatedTenantIds and MCP all inherit it unchanged.
Adding a tenant to a template grants it to all assignees at once (membership-
driven MSP management). Fully app-internal: no Entra group claim / Graph.
- Storage: TenantTemplates (PK=templateId; meta + membership rows) and
TenantTemplateAssignments (PK=upn) tables; meta-backed existence invariant in
both management reads AND the auth hot path (no ghost templates, no scope from
stray rows).
- Offboarding hygiene: property-only wipe now purges TenantTemplates membership
AND DelegatedAdmins rows for the offboarded tenant (closes a pre-existing
orphaned-grant gap).
- Management API: GlobalAdminOnly /api/global/tenant-templates/* (8 routes);
per-affected-tenant audit; all mutations route through the service so the
delegated-scope cache is invalidated in lockstep; existence/membership
invariant owned by the service (no ghost create, no false revoke audits);
templateId normalized case-insensitively at the service boundary.
- Web: SectionTenantTemplates management UI (create/rename/delete, add/remove
tenants, assign/unassign), triple-registered + api.ts block.
Backend 2538 tests green; web tsc clean + vitest 488 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-29 16:17:04 +02:00
..
2026-06-25 17:24:27 +02:00
2026-05-19 18:45:01 +02:00
2026-06-29 16:17:04 +02:00
2026-05-28 17:15:04 +02:00
2026-05-28 12:19:54 +02:00
2026-04-21 11:48:27 +02:00
2026-04-30 22:54:30 +02:00
2026-05-13 01:08:04 +02:00
2026-05-21 15:01:34 +02:00
2026-05-15 12:59:40 +02:00
2026-04-21 10:27:54 +02:00
2026-04-28 19:27:28 +02:00
2026-05-28 17:11:21 +02:00
2026-04-14 21:51:57 +02:00
2026-04-27 19:44:13 +02:00
2026-06-07 18:25:10 +02:00
2026-06-05 21:33:34 +02:00
2026-06-06 11:36:39 +02:00
2026-05-06 20:53:46 +02:00
2026-06-26 10:54:45 +02:00
2026-06-25 21:49:53 +02:00
2026-06-18 18:41:34 +02:00
2026-05-08 15:28:21 +02:00
2026-04-25 12:26:34 +02:00
2026-05-26 15:19:07 +02:00
2026-05-26 15:19:07 +02:00
2026-05-26 16:33:46 +02:00
2026-05-12 19:13:21 +02:00
2026-05-12 23:44:20 +02:00
2026-06-09 22:43:39 +02:00
2026-06-09 22:43:39 +02:00
2026-05-18 23:06:51 +02:00
2026-06-22 20:37:44 +02:00
2026-05-13 12:08:28 +02:00
2026-05-20 19:54:43 +02:00
2026-06-25 19:34:39 +02:00
2026-04-02 01:31:47 +02:00
2026-05-06 20:53:46 +02:00
2026-04-07 09:50:59 +02:00
2026-06-25 18:50:56 +02:00
2026-05-26 16:33:46 +02:00
2026-06-22 19:07:10 +02:00
2026-05-27 12:44:45 +02:00
2026-05-06 20:53:46 +02:00
2026-05-04 18:36:37 +02:00
2026-06-29 16:17:04 +02:00
2026-06-26 19:13:36 +02:00
2026-05-15 12:59:40 +02:00
2026-05-12 19:13:21 +02:00
2026-05-12 19:13:21 +02:00
2026-05-18 23:06:51 +02:00
2026-05-08 15:28:21 +02:00
2026-04-14 20:33:14 +02:00
2026-05-28 17:03:48 +02:00
2026-05-29 16:57:54 +02:00
2026-05-19 18:45:01 +02:00
2026-05-19 18:45:01 +02:00
2026-06-22 21:29:29 +02:00
2026-03-31 17:10:59 +02:00
2026-05-21 14:26:12 +02:00
2026-06-25 20:16:49 +02:00
2026-06-17 18:54:39 +02:00
2026-06-16 17:30:02 +02:00
2026-05-29 23:10:08 +02:00
2026-04-08 16:57:50 +02:00
2026-05-04 09:37:39 +02:00
2026-05-12 23:44:20 +02:00
2026-06-08 14:32:44 +02:00
2026-06-19 14:34:53 +02:00
2026-05-31 15:22:22 +02:00
2026-04-15 00:52:44 +02:00
2026-06-18 18:04:17 +02:00
2026-05-31 14:54:15 +02:00
2026-06-25 20:58:11 +02:00
2026-06-09 13:15:23 +02:00
2026-05-19 18:45:01 +02:00
2026-04-05 13:36:02 +02:00
2026-06-26 23:21:39 +02:00
2026-04-21 10:37:27 +02:00
2026-05-05 13:18:47 +02:00
2026-04-08 13:40:02 +02:00
2026-06-25 17:24:27 +02:00
2026-04-28 20:07:06 +02:00
2026-04-05 13:36:02 +02:00
2026-05-31 16:16:35 +02:00
2026-05-11 14:32:38 +02:00
2026-05-19 18:45:01 +02:00
2026-04-21 12:29:12 +02:00
2026-04-21 11:48:27 +02:00
2026-04-21 11:48:27 +02:00
2026-04-21 12:03:06 +02:00
2026-04-21 12:29:12 +02:00
2026-04-21 11:29:35 +02:00
2026-03-19 17:21:38 +01:00
2026-06-12 13:09:17 +02:00
2026-06-09 22:43:39 +02:00
2026-06-25 17:24:27 +02:00
2026-04-09 19:50:13 +02:00
2026-05-08 16:16:12 +02:00
2026-05-28 18:53:50 +02:00
2026-06-25 10:35:42 +02:00
2026-05-31 18:09:39 +02:00
2026-05-08 18:06:56 +02:00
2026-06-26 23:21:19 +02:00
2026-06-06 15:39:49 +02:00
2026-04-30 08:45:55 +02:00
2026-05-27 16:52:52 +02:00
2026-06-07 18:11:17 +02:00
2026-04-15 00:34:18 +02:00
2026-06-22 21:29:29 +02:00
2026-05-11 19:02:59 +02:00
2026-06-24 17:11:28 +02:00
2026-04-01 02:39:11 +02:00
2026-05-18 23:06:51 +02:00
2026-05-18 23:06:51 +02:00
2026-06-25 10:54:32 +02:00
2026-05-07 09:58:53 +02:00
2026-06-26 18:06:25 +02:00
2026-06-29 16:17:04 +02:00
2026-05-21 15:01:34 +02:00
2026-05-06 20:53:46 +02:00
2026-05-30 13:23:23 +02:00
2026-05-07 10:55:39 +02:00
2026-06-08 13:11:52 +02:00
2026-04-24 13:39:47 +02:00
2026-04-21 10:56:18 +02:00
2026-05-13 12:08:28 +02:00
2026-05-19 12:44:36 +02:00
2026-05-14 11:38:34 +02:00
2026-05-26 17:04:33 +02:00
2026-06-09 22:43:39 +02:00
2026-06-09 22:43:39 +02:00
2026-04-27 21:55:50 +02:00
2026-06-09 22:43:39 +02:00
2026-06-09 22:43:39 +02:00
2026-06-09 22:43:39 +02:00
2026-05-18 23:06:51 +02:00
2026-05-19 18:45:01 +02:00
2026-05-27 20:49:01 +02:00
2026-05-07 10:55:39 +02:00
2026-05-06 20:53:46 +02:00
2026-05-07 11:31:50 +02:00
2026-05-21 14:26:12 +02:00
2026-04-01 02:22:48 +02:00
2026-04-14 23:42:10 +02:00
2026-04-29 19:00:39 +02:00
2026-06-09 22:43:39 +02:00
2026-05-19 18:45:01 +02:00
2026-05-19 18:45:01 +02:00
2026-05-15 12:59:40 +02:00
2026-05-12 21:34:42 +02:00
2026-05-12 21:18:40 +02:00
2026-06-05 14:56:41 +02:00
2026-05-13 01:08:04 +02:00
2026-05-12 20:41:32 +02:00
2026-06-05 14:56:41 +02:00
2026-05-06 20:53:46 +02:00
2026-06-08 13:11:52 +02:00
2026-05-06 20:53:46 +02:00
2026-05-06 20:53:46 +02:00
2026-05-13 20:09:50 +02:00
2026-06-25 10:54:32 +02:00
2026-05-11 14:22:37 +02:00
2026-06-09 22:43:39 +02:00
2026-06-26 19:38:00 +02:00
2026-06-25 12:16:04 +02:00
2026-06-25 12:16:04 +02:00
2026-05-12 19:59:28 +02:00
2026-05-20 19:54:43 +02:00
2026-06-09 21:12:38 +02:00
2026-06-08 13:11:52 +02:00
2026-05-19 13:14:09 +02:00
2026-06-08 17:09:46 +02:00
2026-04-21 10:50:14 +02:00
2026-05-19 12:44:36 +02:00
2026-04-21 11:29:35 +02:00
2026-05-19 12:44:36 +02:00
2026-04-21 10:37:27 +02:00
2026-05-11 18:47:58 +02:00
2026-04-11 11:17:48 +02:00
2026-05-12 19:59:28 +02:00
2026-05-12 19:59:28 +02:00
2026-05-12 23:44:20 +02:00
2026-05-13 10:44:54 +02:00
2026-05-04 18:36:37 +02:00
2026-04-21 14:31:28 +02:00
2026-05-27 19:39:02 +02:00
2026-05-27 19:39:02 +02:00
2026-05-11 18:47:58 +02:00
2026-05-08 18:32:22 +02:00
2026-05-08 18:32:22 +02:00
2026-05-19 12:44:36 +02:00
2026-03-31 22:29:24 +02:00
2026-06-22 21:29:29 +02:00
2026-06-05 20:54:16 +02:00
2026-05-05 12:12:54 +02:00
2026-06-25 12:16:04 +02:00
2026-06-23 19:44:30 +02:00
2026-04-08 13:40:02 +02:00
2026-04-02 22:43:44 +02:00
2026-04-30 15:25:28 +02:00
2026-04-30 15:25:28 +02:00
2026-05-12 19:59:28 +02:00
2026-04-30 15:25:28 +02:00
2026-06-09 22:43:39 +02:00
2026-05-27 17:56:22 +02:00