Awesome Testing

Markdown document

Raport błędu: schematy odpowiedzi 2FA w OpenAPI

Lekcja 23: Automatyzacja two-factor authentication

Historical artifacts may name disposable training credentials and environments. Do not reuse credentials, target course systems, or execute archived prompts without authorization.

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-30
  • https://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

  • 401 responses reference a common API error schema containing a required string message property.
  • 400 Jakarta 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/2fa
  • GET /api/v1/users/2fa/status
  • POST /api/v1/users/2fa/setup
  • POST /api/v1/users/2fa/confirm
  • POST /api/v1/users/2fa/recovery-codes
  • POST /api/v1/users/2fa/disable (no response schema is published for any status, including error statuses)

Reproduction frequency and evidence

EnvironmentScenarioAttemptsResult
aitesters.byst.reunauthenticated GET /2fa/status3 / 3401, JSON {message}, no-store caching
awesome.byst.reunauthenticated GET /2fa/status3 / 3401, JSON {message}, no-store caching
aitesters.byst.reempty-body-object POST /signin/2fa1 / 1400, field-validation JSON
awesome.byst.reempty-body-object POST /signin/2fa1 / 1400, field-validation JSON
both deploymentsunauthenticated POST /2fa/setup1 / 1 each401, 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.

  1. Define reusable OpenAPI schemas for the common {message} error and validation error map.
  2. Reference the correct error content explicitly for every documented 2FA non-success status; document POST /2fa/disable success as an empty body.
  3. Add a backend OpenAPI regression test that resolves every 2FA response schema and compares it with representative MockMvc response bodies.
  4. 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.