Skip to content

fix(auth): /client/authenticate tampoco responde 404, y el ADR-0165 dice qué oráculo era cada uno - #18

Merged
beyondnetPeru merged 1 commit into
mainfrom
fix/client-auth-404-y-adr-0165
Aug 10, 2026
Merged

fix(auth): /client/authenticate tampoco responde 404, y el ADR-0165 dice qué oráculo era cada uno#18
beyondnetPeru merged 1 commit into
mainfrom
fix/client-auth-404-y-adr-0165

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Continuación de #16, salida de reportarlo aguas arriba (unimar-peru/unimar-ums#214): al verificar el código antes de escribir la acusación aparecieron dos cosas que #16 no cerró.

1. El mismo 404 estaba en el endpoint que #16 citaba como ejemplo

ClientAuthEndpoints.GetStatusCode mapeaba AUTH_004 a 404 mientras colapsaba a 401 todo lo demás de nivel usuario o inquilino.

Ahí duele más que en el portal. /client/authenticate persigue la indistinguibilidad como diseño explícito: SpanishMessage devuelve la misma cadena exacta para AUTH_002, AUTH_003, AUTH_004, AUTH_006 y AUTH_017, y no hay ningún humano al otro lado a quien darle la pista. Con el cuerpo ya uniformado, el status era la única señal que quedaba en pie — y decía «el IdP te autenticó pero aquí no tienes cuenta», que es justo lo que el mensaje se negaba a decir.

El comentario de G-053 en ese método afirmaba que «AUTH_004/005 conservan su semántica». AUTH_005 sí había colapsado a 401; AUTH_004 se quedó en 404 y nadie lo notó. Ningún test afirmaba ese 404: se pudo cambiar sin tocar una sola aserción, lo que es parte del hallazgo.

G-053 no se amplía ni se reinterpreta: se termina de aplicar donde ya regía. Queda como §2.4 del ADR-0165.

2. AUTH_004 no es «el usuario no existe» — el ADR describía una amenaza que este código no tiene

#16 dio por sentado que AUTH_004 era «usuario inexistente» y de ahí dedujo el barrido anónimo de una lista de correos. El código dice otra cosa:

Rama Usuario inexistente Código
Local (AuthenticateLocalAsync) devuelve lo mismo que una contraseña mala AUTH_006
Federada (AuthenticateIdpAsync) tras autenticar con éxito contra el IdP AUTH_004

Es decir: en la rama local nunca hubo nada que filtrar, y para llegar al 404 de AUTH_004 hay que traer credenciales válidas del IdP. No es el barrido anónimo de una lista de correos; es un oráculo para quien ya está dentro — alguien con cuenta en el IdP corporativo puede recorrer inquilinos y averiguar en cuáles tiene cuenta UMS una identidad federada. Sigue siendo lo que el mensaje calla a propósito y el estado regalaba, y sigue mereciendo el 401, pero por la razón correcta.

El oráculo anónimo y barato era AUTH_002, no AUTH_004: la búsqueda del inquilino precede a cualquier credencial, así que separa códigos de inquilino reales de inventados a una petición por candidato y sin credencial de ningún tipo. Y ése §2.3 lo deja abierto a propósito, porque el cuerpo ya lo revela. Conviene que el ADR no se lea como si cerrara un barrido que no cierra: alguien lo citará dentro de un año.

Corregido en las dos versiones del ADR (ES/EN) y en la ficha TD-009.

Alcance

Un solo cambio de comportamiento: /api/v1/client/authenticate devuelve 401 en vez de 404 cuando el IdP autentica a alguien sin cuenta UMS en el inquilino. El cuerpo no cambia (ya era el mensaje genérico). El SDK no mira ese status para distinguir nada.

Verificación

  • Ums.ContractTest38/38
  • Ums.Presentation.IntegrationTest314/316 (2 saltados, los mismos de siempre)
  • dotnet build Ums.sln — 0 errores

🤖 Generated with Claude Code

…ice qué oráculo era cada uno

Dos correcciones al cambio anterior, ambas salidas de reportarlo aguas arriba
(unimar-peru/unimar-ums#214), donde hubo que verificar el código antes de escribir
la acusación.

## El mismo 404 estaba en el endpoint que se citaba como ejemplo

`ClientAuthEndpoints.GetStatusCode` mapeaba AUTH_004 a 404 mientras colapsaba a 401
todo lo demás de nivel usuario o inquilino. Ahí duele más que en el portal: ese
endpoint persigue la indistinguibilidad como diseño explícito —`SpanishMessage`
devuelve la MISMA cadena exacta para AUTH_002, AUTH_003, AUTH_004, AUTH_006 y
AUTH_017, y no hay humano al otro lado a quien darle la pista—, así que el status
era la única señal que quedaba en pie. El comentario de G-053 en ese método decía
que «AUTH_004/005 conservan su semántica»: AUTH_005 sí había colapsado, AUTH_004 se
quedó en 404 y nadie lo notó. Ningún test lo afirmaba, que es parte del hallazgo.

## AUTH_004 no es «el usuario no existe»

El ADR lo daba por sentado y de ahí deducía un barrido anónimo de listas de correo.
El código dice otra cosa: en la rama local un usuario inexistente devuelve AUTH_006
—el mismo código que una contraseña equivocada—, así que ahí nunca hubo nada que
filtrar. AUTH_004 sólo se emite en la rama federada y DESPUÉS de que el IdP haya
autenticado con éxito: para ver ese 404 hay que traer credenciales válidas. Es un
oráculo para quien ya está dentro (qué identidades federadas tienen cuenta UMS en
qué inquilino), no para un anónimo. Sigue mereciendo el 401 —el mensaje calla eso a
propósito y el estado lo regalaba—, pero por la razón correcta.

El oráculo anónimo y barato era AUTH_002: la búsqueda del inquilino precede a
cualquier credencial, así que separa códigos reales de inventados a petición por
candidato sin credencial alguna. Y ése §2.3 lo deja abierto a propósito, porque el
cuerpo ya lo revela. Conviene que el ADR no se lea como si cerrara un barrido que no
cierra.

Verificado: contrato 38/38, integración 314/316 (2 saltados), build 0 errores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@beyondnetPeru
beyondnetPeru merged commit 7db85a5 into main Aug 10, 2026
24 checks passed
@beyondnetPeru
beyondnetPeru deleted the fix/client-auth-404-y-adr-0165 branch August 10, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant