Skip to content

Commit 13073f7

Browse files
committed
docs: formalize tenant portal authorization boundary
1 parent 1c5bc89 commit 13073f7

13 files changed

Lines changed: 360 additions & 9 deletions

docs/architecture/adrs/0071-auth-graph-engine.es.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [ADR-0061: Execution Context Accessor](./0061-execution-context-accessor.md)
99
- [ADR-0068: Alcance del Sistema de Feature Flags](./0068-feature-flag-system-scope.md)
1010
- [ADR-0072: Resolución Dinámica del Método de Autenticación](./0072-dynamic-auth-method-resolution.es.md)
11+
- [ADR-0077: Frontera de Autorizacion para Gestion del Portal del Tenant](./0077-tenant-portal-management-authorization-boundary.es.md)
1112

1213
---
1314

docs/architecture/adrs/0071-auth-graph-engine.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [ADR-0061: Execution Context Accessor](./0061-execution-context-accessor.md)
99
- [ADR-0068: Feature Flag System Scope](./0068-feature-flag-system-scope.md)
1010
- [ADR-0070: Dynamic Auth Method Resolution](./0070-dynamic-auth-method-resolution.md)
11+
- [ADR-0077: Tenant Portal Management Authorization Boundary](./0077-tenant-portal-management-authorization-boundary.md)
1112

1213
---
1314

docs/architecture/adrs/0072-dynamic-auth-method-resolution.es.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
**Relacionados:**
77
- [ADR-0071: Motor del Grafo de Autorización](./0071-auth-graph-engine.es.md)
88
- [ADR-0054: Aislamiento de Shell Libraries](./0054-shell-library-isolation.md)
9+
- [ADR-0077: Frontera de Autorizacion para Gestion del Portal del Tenant](./0077-tenant-portal-management-authorization-boundary.es.md)
910

1011
---
1112

docs/architecture/adrs/0072-dynamic-auth-method-resolution.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
**Related:**
77
- [ADR-0071: Auth Graph Engine](./0071-auth-graph-engine.md)
88
- [ADR-0054: Shell Library Isolation](./0054-shell-library-isolation.md)
9+
- [ADR-0077: Tenant Portal Management Authorization Boundary](./0077-tenant-portal-management-authorization-boundary.md)
910

1011
---
1112

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# ADR-0077: Frontera de Autorizacion para Gestion del Portal del Tenant y Politica de Scope
2+
3+
**Estado:** Accepted
4+
**Fecha:** 2026-06-02
5+
**Decision Owner:** Architecture
6+
**Relacionados:**
7+
- [ADR-0071: Motor de Grafo de Autorizacion](./0071-auth-graph-engine.es.md)
8+
- [ADR-0072: Resolucion Dinamica del Metodo de Autenticacion](./0072-dynamic-auth-method-resolution.es.md)
9+
- [ADR-0060: Estrategia de AOP para Concerns Transversales](./0060-aop-cross-cutting-concern-strategy.es.md)
10+
- [ADR-0075: Bandeja de Aprobacion de Onboarding y Autorizacion por Alcance](./0075-onboarding-approval-inbox-and-scope-based-authorization.es.md)
11+
12+
---
13+
14+
## Contexto
15+
16+
UMS debe soportar dos responsabilidades de autorizacion distintas que pueden confundirse facilmente si se implementan con el mismo mecanismo:
17+
18+
| Flujo | Proposito | Regla rectora |
19+
|---|---|---|
20+
| Gestion del portal | Un usuario del tenant inicia sesion en el portal de UMS para ver o administrar sus propios datos permitidos | Debe autorizarse por scope de tenant, roles, permisos y `Tenant.IsManagementOwner` |
21+
| API publica externa | Un cliente downstream se autentica contra la API publica de autenticacion | Debe respetar el metodo de autenticacion configurado del tenant y el grafo de autorizacion |
22+
23+
Historicamente, el AOP transversal ha parecido un lugar conveniente para imponer checks de acceso porque ya esta unido a los command handlers. Esa es la frontera equivocada para una decision de seguridad. La autorizacion es critica para el negocio, debe permanecer explicita y debe ser facil de inspeccionar en tests y code reviews.
24+
25+
Por eso el flujo del portal necesita una frontera interna de gestion separada del flujo de autenticacion de la API publica. La frontera de gestion debe ser consciente del tenant, leer el flag de propiedad del tenant desde el modelo de dominio y evitar que cualquier tenant opere fuera de su scope.
26+
27+
---
28+
29+
## Decision
30+
31+
UMS tratara la autorizacion de gestion del portal como una politica explicita de aplicacion, no como una preocupacion de AOP.
32+
33+
| Area de Decision | Eleccion |
34+
|---|---|
35+
| Scope del portal | Introducir `AuthAccessScope.PortalManagement` para el acceso al portal y `AuthAccessScope.ExternalApi` para la API publica de autenticacion. |
36+
| Regla de gestion interna | Usar `ITenantScopePolicy` para validar propiedad del tenant y scope antes de ejecutar comandos mutantes del portal. |
37+
| Propiedad del tenant | Usar `Tenant.IsManagementOwner` como el flag autoritativo que habilita acciones de gestion interna para un tenant. |
38+
| Autenticacion API externa | Mantener `IAuthMethodResolver.ResolveAsync(tenantId, scope)` como el resolvedor explicito para la API publica de autenticacion. |
39+
| Uso de AOP | Reservar AOP para logging, tracing, enrichment de auditoria y otros concerns transversales. Nunca usarlo como frontera principal de autorizacion. |
40+
| Scope de consultas | Permitir que los query handlers usen la policy para resolver el scope visible, pero mantener la autorizacion de escritura explicita en el boundary del comando. |
41+
42+
---
43+
44+
## Modelo de Arquitectura
45+
46+
```mermaid
47+
flowchart TB
48+
subgraph Presentation["Presentation"]
49+
P1["AuthEndpoints"]
50+
P2["ClientAuthEndpoints"]
51+
P3["Endpoints de gestion"]
52+
P4["GraphQL / Middleware"]
53+
end
54+
55+
subgraph Application["Application"]
56+
A1["AuthMethodResolverService"]
57+
A2["TenantScopePolicy"]
58+
A3["Command handlers"]
59+
A4["Query handlers"]
60+
A5["Aspectos AOP de logging"]
61+
end
62+
63+
subgraph Domain["Domain"]
64+
D1["Tenant"]
65+
D2["AuthAccessScope"]
66+
D3["AuthorizationGraph"]
67+
D4["Invariantes de negocio"]
68+
end
69+
70+
subgraph Infrastructure["Infrastructure"]
71+
I1["TenantRepository"]
72+
I2["Proveedores de configuracion"]
73+
I3["Seeders / migraciones"]
74+
end
75+
76+
P1 --> A1
77+
P2 --> A1
78+
P3 --> A2
79+
P3 --> A3
80+
P4 --> A4
81+
A1 --> D2
82+
A2 --> D1
83+
A3 --> D1
84+
A4 --> D1
85+
A1 --> I2
86+
A2 --> I1
87+
I1 --> D1
88+
I2 --> A1
89+
A5 -. solo concern transversal .-> A3
90+
A5 -. solo concern transversal .-> A4
91+
A3 --> D3
92+
```
93+
94+
```mermaid
95+
sequenceDiagram
96+
participant User as Usuario del portal
97+
participant Portal as Portal UMS
98+
participant Handler as Command handler
99+
participant Policy as TenantScopePolicy
100+
participant Tenant as Agregado Tenant
101+
102+
User->>Portal: Inicia sesion en el portal de UMS
103+
Portal->>Handler: AuthenticateUserCommand(PortalManagement)
104+
Handler-->>Portal: Sesion + grafo
105+
106+
User->>Portal: Crea o modifica un recurso del tenant
107+
Portal->>Handler: Comando mutante
108+
Handler->>Policy: EnsureManagementOwnerScopeAsync(tenantId)
109+
Policy->>Tenant: Cargar el tenant e inspeccionar IsManagementOwner
110+
Tenant-->>Policy: true / false
111+
Policy-->>Handler: Permitido / denegado
112+
Handler-->>Portal: Resultado
113+
```
114+
115+
---
116+
117+
## Por Que Esta Decision
118+
119+
1. Mantiene las decisiones de seguridad explicitas en la capa de aplicacion.
120+
2. Evita convertir AOP en un motor de autorizacion oculto.
121+
3. Alinea la gestion del portal con la propiedad del tenant en vez de mezclarla con el flujo publico de resolucion de IDP.
122+
4. Preserva la testabilidad porque las decisiones de scope pueden cubrirse con pruebas unitarias directas.
123+
5. Mantiene intacto el flujo de la API externa para clientes downstream.
124+
6. Reduce el riesgo de que el acceso al portal y la autenticacion externa se influyan accidentalmente.
125+
126+
---
127+
128+
## Alternativas Consideradas
129+
130+
| Alternativa | Decision | Motivo |
131+
|---|---|---|
132+
| Hacer cumplir el acceso al tenant en aspectos AOP | Rechazada | Las reglas de seguridad quedarian implicitas y mas dificiles de auditar. |
133+
| Hacer cumplir el acceso al tenant solo en middleware | Rechazada | Middleware es demasiado temprano y demasiado grueso para varias reglas de negocio. |
134+
| Mezclar la gestion del portal y la autenticacion externa en un solo flujo | Rechazada | Los dos casos de uso tienen fronteras de confianza y reglas de negocio distintas. |
135+
| Guardar la capacidad de gestion fuera de `Tenant` | Rechazada | La propiedad del tenant debe ser la fuente de verdad para el scope de gestion interna. |
136+
137+
---
138+
139+
## Consecuencias
140+
141+
### Positivas
142+
143+
- Las reglas de acceso del portal son faciles de encontrar en la capa de aplicacion.
144+
- Las pruebas de autorizacion siguen siendo enfocadas y legibles.
145+
- La propiedad de gestion del tenant es visible en el modelo de dominio.
146+
- La API externa sigue respetando la configuracion de IDP especifica del tenant.
147+
148+
### Compromisos
149+
150+
- Algunos handlers deben llamar explicitamente a la policy en vez de depender de un interceptor global.
151+
- La base de codigo necesita disciplina para mantener AOP limitado a concerns transversales.
152+
- Un tenant marcado como management owner sigue requiriendo checks de rol y permiso para cada operacion.
153+
154+
---
155+
156+
## Notas de Implementacion
157+
158+
| Area | Guia |
159+
|---|---|
160+
| Commands | Los comandos mutantes del portal deben validar `ITenantScopePolicy` antes de cualquier escritura. |
161+
| Queries | Los query handlers pueden usar la policy para resolver el scope visible, pero nunca para saltarse la autorizacion. |
162+
| Auth | Usar `AuthAccessScope.PortalManagement` para el login del portal y `AuthAccessScope.ExternalApi` para la API publica de autenticacion. |
163+
| Domain | Mantener `Tenant.IsManagementOwner` como la fuente de verdad para la capacidad de gestion interna. |
164+
| AOP | Usar `LoggerAspect` y aspectos relacionados solo para instrumentacion, no para conceder acceso. |
165+
| Trazabilidad | Enlazar este ADR desde historias funcionales y documentacion de dominio que hablen de gestion del portal del tenant. |
166+
167+
---
168+
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# ADR-0077: Tenant Portal Management Authorization Boundary and Scope Policy
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-02
5+
**Decision Owner:** Architecture
6+
**Related:**
7+
- [ADR-0071: Auth Graph Engine](./0071-auth-graph-engine.md)
8+
- [ADR-0072: Dynamic Auth Method Resolution](./0072-dynamic-auth-method-resolution.md)
9+
- [ADR-0060: AOP Cross-Cutting Concern Strategy](./0060-aop-cross-cutting-concern-strategy.md)
10+
- [ADR-0075: Onboarding Approval Inbox and Scope-Based Authorization](./0075-onboarding-approval-inbox-and-scope-based-authorization.md)
11+
12+
---
13+
14+
## Context
15+
16+
UMS must support two different authorization responsibilities that are easy to confuse if they are implemented with the same mechanism:
17+
18+
| Flow | Purpose | Governing rule |
19+
|---|---|---|
20+
| Portal management | A tenant user signs into the UMS portal to view or manage their own allowed data | Must be authorized by tenant scope, roles, permissions, and `Tenant.IsManagementOwner` |
21+
| Public external API | A downstream client authenticates against the public auth API | Must honor the tenant's configured auth method and the auth graph |
22+
23+
Historically, cross-cutting AOP was a tempting place to enforce access checks because it is already attached to command handlers. That would be the wrong boundary for a security decision. Authorization is business-critical, must remain explicit, and must be easy to inspect in tests and code reviews.
24+
25+
The portal flow therefore needs an internal management boundary that is separate from the external API authentication flow. The management boundary must be tenant-aware, read the tenant ownership flag from the domain model, and prevent any tenant from operating outside its scope.
26+
27+
---
28+
29+
## Decision
30+
31+
UMS will treat portal management authorization as an explicit application policy, not as an AOP concern.
32+
33+
| Decision Area | Choice |
34+
|---|---|
35+
| Portal scope | Introduce `AuthAccessScope.PortalManagement` for portal access and `AuthAccessScope.ExternalApi` for the public auth API. |
36+
| Internal management rule | Use `ITenantScopePolicy` to validate tenant ownership and scope before executing mutating portal commands. |
37+
| Tenant ownership | Use `Tenant.IsManagementOwner` as the authoritative flag that enables internal management actions for a tenant. |
38+
| External API auth | Keep `IAuthMethodResolver.ResolveAsync(tenantId, scope)` as the explicit resolver for the public authentication API. |
39+
| AOP usage | Reserve AOP for logging, tracing, audit enrichment, and other cross-cutting concerns. Never use it as the primary authorization boundary. |
40+
| Query scope | Allow query handlers to use the policy for scope resolution, but keep write authorization explicit at the command boundary. |
41+
42+
---
43+
44+
## Architecture Model
45+
46+
```mermaid
47+
flowchart TB
48+
subgraph Presentation["Presentation"]
49+
P1["AuthEndpoints"]
50+
P2["ClientAuthEndpoints"]
51+
P3["Management endpoints"]
52+
P4["GraphQL / Middleware"]
53+
end
54+
55+
subgraph Application["Application"]
56+
A1["AuthMethodResolverService"]
57+
A2["TenantScopePolicy"]
58+
A3["Command handlers"]
59+
A4["Query handlers"]
60+
A5["AOP logging aspects"]
61+
end
62+
63+
subgraph Domain["Domain"]
64+
D1["Tenant"]
65+
D2["AuthAccessScope"]
66+
D3["AuthorizationGraph"]
67+
D4["Business invariants"]
68+
end
69+
70+
subgraph Infrastructure["Infrastructure"]
71+
I1["TenantRepository"]
72+
I2["Configuration providers"]
73+
I3["Seeders / migrations"]
74+
end
75+
76+
P1 --> A1
77+
P2 --> A1
78+
P3 --> A2
79+
P3 --> A3
80+
P4 --> A4
81+
A1 --> D2
82+
A2 --> D1
83+
A3 --> D1
84+
A4 --> D1
85+
A1 --> I2
86+
A2 --> I1
87+
I1 --> D1
88+
I2 --> A1
89+
A5 -. cross-cutting only .-> A3
90+
A5 -. cross-cutting only .-> A4
91+
A3 --> D3
92+
```
93+
94+
```mermaid
95+
sequenceDiagram
96+
participant User as Portal user
97+
participant Portal as UMS portal
98+
participant Handler as Command handler
99+
participant Policy as TenantScopePolicy
100+
participant Tenant as Tenant aggregate
101+
102+
User->>Portal: Sign in to the UMS portal
103+
Portal->>Handler: AuthenticateUserCommand(PortalManagement)
104+
Handler-->>Portal: Session + graph
105+
106+
User->>Portal: Create or update tenant-owned resource
107+
Portal->>Handler: Mutating command
108+
Handler->>Policy: EnsureManagementOwnerScopeAsync(tenantId)
109+
Policy->>Tenant: Load tenant and inspect IsManagementOwner
110+
Tenant-->>Policy: true / false
111+
Policy-->>Handler: Allowed / denied
112+
Handler-->>Portal: Result
113+
```
114+
115+
---
116+
117+
## Why This Decision
118+
119+
1. It keeps security decisions explicit in the application layer.
120+
2. It avoids turning AOP into a hidden authorization engine.
121+
3. It aligns portal management with tenant ownership rather than with the public IDP resolution path.
122+
4. It preserves testability because scope decisions can be covered with direct unit tests.
123+
5. It keeps the external API flow fully intact for downstream clients.
124+
6. It reduces the chance that portal access and external authentication will accidentally influence each other.
125+
126+
---
127+
128+
## Alternatives Considered
129+
130+
| Alternative | Decision | Reason |
131+
|---|---|---|
132+
| Enforce tenant access in AOP aspects | Rejected | Security rules would become implicit and harder to audit. |
133+
| Enforce tenant access only in middleware | Rejected | Middleware is too early and too coarse for many business rules. |
134+
| Merge portal management and external API auth into one flow | Rejected | The two use cases have different trust boundaries and different business rules. |
135+
| Store management access outside `Tenant` | Rejected | Tenant ownership must be the source of truth for internal management scope. |
136+
137+
---
138+
139+
## Consequences
140+
141+
### Positive
142+
143+
- Portal access rules are easy to find in the application layer.
144+
- Authorization tests remain focused and readable.
145+
- Tenant management ownership is visible in the domain model.
146+
- The external API still honors tenant-specific IDP configuration.
147+
148+
### Trade-offs
149+
150+
- Some handlers must explicitly call the policy instead of relying on a global interceptor.
151+
- The codebase needs discipline to keep AOP limited to cross-cutting concerns.
152+
- A tenant marked as management owner still requires role and permission checks for each operation.
153+
154+
---
155+
156+
## Implementation Notes
157+
158+
| Area | Guidance |
159+
|---|---|
160+
| Commands | Mutating portal commands must validate `ITenantScopePolicy` before any write occurs. |
161+
| Queries | Query handlers may use the policy to resolve the visible scope, but never to bypass authorization. |
162+
| Auth | Use `AuthAccessScope.PortalManagement` for portal login and `AuthAccessScope.ExternalApi` for the public auth API. |
163+
| Domain | Keep `Tenant.IsManagementOwner` as the source of truth for internal management capability. |
164+
| AOP | Use `LoggerAspect` and related aspects only for instrumentation, not for granting access. |
165+
| Traceability | Link this ADR from functional stories and domain documentation that talk about tenant portal management. |
166+
167+
---
168+

docs/architecture/adrs/index.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,16 @@ UMS is a satellite repository of `evolith_arch32`. The parent repository defines
5656
| [ADR-0073](./0073-ums-sdk-multi-runtime.md) | UMS SDK — Multi-Runtime Client Integration Surface (.NET / TypeScript / NestJS) | Accepted |
5757
| [ADR-0074](./0074-auth-graph-schema-versioning.md) | Auth Graph Schema Versioning Policy | Accepted |
5858
| [ADR-0075](./0075-onboarding-approval-inbox-and-scope-based-authorization.md) | Onboarding Approval Inbox and Scope-Based Authorization | Accepted |
59+
| [ADR-0076](./0076-utc-dates-timezone-language-resolution.md) | UTC Dates, Timezone and Language Resolution | Accepted |
60+
| [ADR-0077](./0077-tenant-portal-management-authorization-boundary.md) | Tenant Portal Management Authorization Boundary and Scope Policy | Accepted |
5961

6062
> **Evolith candidate** - ADR has zero UMS-specific dependencies and is proposed for extraction to the Evolith parent architecture baseline.
6163
6264
---
6365

6466
## Bilingual Coverage (R-01 Compliance)
6567

66-
All ADRs (0050-0070) now have Spanish translations:
68+
All ADRs (0050-0077) now have Spanish translations:
6769

6870
| ADR | Spanish | ADR | Spanish |
6971
|-----|---------|-----|---------|
@@ -80,6 +82,9 @@ All ADRs (0050-0070) now have Spanish translations:
8082
| 0060 || 0072 ||
8183
||| 0073 ||
8284
||| 0074 ||
85+
||| 0075 ||
86+
||| 0076 ||
87+
||| 0077 ||
8388

8489
---
8590

0 commit comments

Comments
 (0)