Bug: 2FA OpenAPI error responses use success DTO schemas
Summary and impact
All six two-factor authentication operations have incorrect or incomplete
non-success response schemas. Five publish the operation's success DTO for errors,
while POST /2fa/disable publishes no response content schema. Live validation
and authentication failures instead return either a field-validation object or
the common { "message": "..." } error shape.
This makes the generated OpenAPI contract unsafe for client generation, schema
validation, and response-path test generation. A client generated from the
published document can attempt to deserialize an error as LoginResponseDto,
MfaSetupResponseDto, MfaRecoveryCodesResponseDto, or
MfaStatusResponseDto.
Environment and observation date
https://aitesters.byst.re— observed 2026-08-30https://awesome.byst.re— observed 2026-08-30- Live contract source on both deployments:
/v3/api-docs - Authentication: no credentials required for the reproductions below
Preconditions
None. The reproduction uses only safe unauthenticated or validation requests and does not create data.
Steps to reproduce
Fetch the live operation schemas:
curl --silent --show-error --fail \
https://awesome.byst.re/v3/api-docs \
| jq '.paths["/api/v1/users/2fa/status"].get.responses["401"]'
The published 401 content references MfaStatusResponseDto. Then call the
operation without a bearer token:
curl --include \
https://awesome.byst.re/api/v1/users/2fa/status
The validation variant is also directly observable:
curl --include \
--header 'Content-Type: application/json' \
--data '{}' \
https://awesome.byst.re/api/v1/users/signin/2fa
Repeat the same commands with https://aitesters.byst.re to reproduce the
staging result.
Expected result
401responses reference a common API error schema containing a required stringmessageproperty.400Jakarta validation responses reference a field-error map schema, or a documented validation-error DTO matching the actual body.- Success DTOs are referenced only by success responses.
Actual result
GET /api/v1/users/2fa/status publishes its 401 response as
MfaStatusResponseDto, but both deployments return:
{"message":"Unauthorized"}
POST /api/v1/users/signin/2fa publishes its 400 response as
LoginResponseDto, but both deployments return:
{"challengeToken":"must not be blank","code":"must not be blank"}
The same success-schema reuse is present in the published non-success responses for:
POST /api/v1/users/signin/2faGET /api/v1/users/2fa/statusPOST /api/v1/users/2fa/setupPOST /api/v1/users/2fa/confirmPOST /api/v1/users/2fa/recovery-codesPOST /api/v1/users/2fa/disable(no response schema is published for any status, including error statuses)
Reproduction frequency and evidence
| Environment | Scenario | Attempts | Result |
|---|---|---|---|
aitesters.byst.re | unauthenticated GET /2fa/status | 3 / 3 | 401, JSON {message}, no-store caching |
awesome.byst.re | unauthenticated GET /2fa/status | 3 / 3 | 401, JSON {message}, no-store caching |
aitesters.byst.re | empty-body-object POST /signin/2fa | 1 / 1 | 400, field-validation JSON |
awesome.byst.re | empty-body-object POST /signin/2fa | 1 / 1 | 400, field-validation JSON |
| both deployments | unauthenticated POST /2fa/setup | 1 / 1 each | 401, JSON {message} |
The 401 /2fa/status responses used
application/json;charset=UTF-8 and
cache-control: no-cache, no-store, max-age=0, must-revalidate.
OpenAPI/live comparison
Both live contracts expose the same six 2FA operations and the same documented status sets. Their response descriptions are useful, but generated response content for DTO-returning methods inherits the controller method's success return type rather than the runtime exception or validation shape; the void-returning disable operation gets no error schema.
The repository-level api-docs.json has only 42 operations and contains none of
the 2FA paths, so it cannot be used as the current denominator or as a fallback
schema source.
Cleanup outcome
No data was created by the isolated defect reproductions, so no cleanup was
required. Separate end-to-end PoCs used generated accounts and deleted both
accounts successfully with 204.
Investigation leads and suggested backend regression coverage
The likely source is SpringDoc response inference from the controller return type:
the @ApiResponse annotations provide descriptions but do not declare explicit
error content schemas. This is an inference from UserMfaController and should
be confirmed against the generated document.
- Define reusable OpenAPI schemas for the common
{message}error and validation error map. - Reference the correct error content explicitly for every documented 2FA
non-success status; document
POST /2fa/disablesuccess as an empty body. - Add a backend OpenAPI regression test that resolves every 2FA response schema and compares it with representative MockMvc response bodies.
- Check other controller groups for the same SpringDoc success-schema reuse.
Playwright guidance
Do not block behavioral 2FA automation on this documentation defect. Playwright tests should assert the verified live error bodies and link this report from the coverage plan. Add contract-schema assertions after the OpenAPI document is fixed; do not encode the incorrect success DTOs as accepted error schemas.
