Skip to content

docs(mfa): document the in-app MFA recovery endpoints in the OpenAPI spec#6665

Merged
otavio merged 3 commits into
masterfrom
feat/mfa-inapp-reset-disable
Jul 14, 2026
Merged

docs(mfa): document the in-app MFA recovery endpoints in the OpenAPI spec#6665
otavio merged 3 commits into
masterfrom
feat/mfa-inapp-reset-disable

Conversation

@geovannewashington

@geovannewashington geovannewashington commented Jul 14, 2026

Copy link
Copy Markdown
Member

What

Spec + a forced test fix. Updates the OpenAPI spec to match the cloud
backend changes for in-app MFA recovery, plus one frontend test fix the
spec change forces (the generated SDK is rebuilt from the spec at build
time, so the new token field breaks a stale test mock). No runtime
behaviour changes. Commits on this branch:

  1. docs(mfa): document the in-app MFA reset token in the OpenAPI spec (team#87)
  2. docs(mfa): document the emailed reset codes on disableMFA (team#88)
  3. test(mfa): update mfaResetStore mocks for the new reset token type

Why

The backend gained a new response field and now accepts new request inputs.
Leaving the spec unchanged would let it lie about the API: generated clients
and contract tests would not know about the token response or the new
disable inputs.

Changes

reset flow

  • POST /api/user/mfa/reset: the 200 response now documents the token
    body (marked required, since a success always returns it).
  • PUT /api/user/mfa/reset/{user-id}: the user-id path param is
    documented as accepting either the opaque token (in-app flow) or the raw
    user ID (emailed-link flow), and the operation description reflects that.
    Also fixed an auto-generated POST -> PUT in the description.
    Build fix (SDK ripple)
  • The console's generated SDK (src/client/, gitignored, rebuilt from the
    spec) now types requestResetMfa as returning { token }. Updated the
    mfaResetStore test mocks to match, keeping tsc -b green. Type-only;
    the store's actual re-wire to use the token stays in the frontend
    follow-up (see below)

disable flow

  • PUT /api/user/mfa/disable: the mfaDisable request schema gains optional
    main_email_code / recovery_email_code fields, mirroring mfaReset.
    They are optional, not required: the endpoint takes one of a TOTP code, a
    recovery code, or the two email codes, so marking them required would
    misdescribe the other paths. The operation description lists the three
    options.

Testing

  • Both edited files parse as valid YAML.
  • Semantic OpenAPI lint (redocly/openapi toolchain) was not run locally, as
    it runs in a container rather than on the host. Worth running in CI or
    before merge if there is a spec-lint target.

Companion PR

shellhub-io/cloud#2430

The backend implementation lives in shellhub-io/cloud (branch
feat/mfa-inapp-reset-disable): the opaque reset token and the emailed-code
DisableMFA support this spec documents.

Follow-up

The frontend re-wire in shellhub/ui/ (restore the reset-verify page and
the disable-via-email-codes screen) is still required for these flows to be
usable in the product. Tracked with the backend PR.

Refs: shellhub-io/team#87
Refs: shellhub-io/team#88

The cloud backend now returns an opaque token from POST /user/mfa/reset
and accepts that token (or a raw user ID) on the completion endpoint, so
the published contract needs to match.

The reset-request 200 response now documents the returned `token`, marked
required since a success always carries it. The completion endpoint's
`user-id` path segment is documented as accepting either the opaque token
from the in-app flow or the raw user ID from the emailed reset link, and
its operation description reflects that dual behavior.

Fixes: shellhub-io/team#87
The cloud DisableMFA endpoint now accepts the two codes emailed by
requestResetMFA, so the published contract needs to match.

The mfaDisable request schema gains optional `main_email_code` and
`recovery_email_code` fields, mirroring the mfaReset wording. They are
left optional, not required: the endpoint takes one of a TOTP code, a
recovery code, or the two email codes, so marking them required would
misdescribe the TOTP and recovery-code paths. The operation description
is updated to spell out the three options.

Fixes: shellhub-io/team#88
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Claude finished @geovannewashington's task in 1m 43s —— View job


Code Review Complete

Reviewed 4 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is.

I verified the spec against the cloud backend (cloud/internal/cloud/) and every documented claim matches the implementation:

  • POST /api/user/mfa/resetRequestResetMFA returns &responses.RequestResetMFA{Token: token} ({"token": ...}), always present on success, so documenting token as required in the 200 body is correct.
  • PUT /api/user/mfa/reset/{user-id}service.ResetMFA looks the user-id segment up as a cache token and falls back to a raw user ID on a miss, exactly as the new param/description describe.
  • PUT /api/user/mfa/disablerequests.DisableMFA gained main_email_code / recovery_email_code with no validate:"required" tag, and service.DisableMFA treats them as a third alternative alongside TOTP and recovery codes — so keeping them optional in mfaDisable (unlike the required pair in mfaReset) is the right call. The "must be sent together" note matches validateResetMFACodes, which fails unless both are present and match.

All four YAML files are well-formed and internally consistent.

If you push additional changes and want a new review, tag @shellhub-io/admin and a team member can trigger it.

The reset-request OpenAPI change adds a `token` to the response, so the
generated SDK now types requestResetMfa as returning `{ token: string }`
instead of an empty body. The mfaResetStore test still mocked the old
empty (`null`) shape, which failed `tsc -b` in CI.

Update the requestResetMfa mocks to resolve with `{ token }`. This is a
type-only fix to keep the build green; the store still stores the typed
identifier, so the in-app reset behaviour is unchanged and its re-wire to
actually use the token remains a follow-up.
@geovannewashington
geovannewashington requested a review from a team as a code owner July 14, 2026 18:51
@otavio

otavio commented Jul 14, 2026

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Claude finished @otavio's task in 51s —— View job


Code Review Complete

Reviewed 5 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is.

I verified the spec changes are well-formed and internally consistent, and cross-checked them against the frontend touched by this PR:

  • mfaResetStore.test.ts — the new mockSdkResponse({ token: "reset-token" }) and SdkResponse<{ token: string }> mocks are type-only and match the store's actual behavior: requestMfaReset still keys off the identifier (not the returned token), so the assertions on mfaResetUserId/mfaResetIdentifier remain correct. The token-consuming re-wire is correctly left to the frontend follow-up.
  • mfaDisable.yamlmain_email_code / recovery_email_code are kept optional (unlike the required pair in mfaReset), which correctly reflects that disable accepts TOTP, a recovery code, or the two email codes.
  • reset paths — the token 200 body (required) and the user-id path param accepting either an opaque token or a raw user ID line up with the documented in-app / emailed-link flows.

All edited YAML files parse cleanly and no runtime behaviour changes.

If you push additional changes and want a new review, tag @shellhub-io/admin and a team member can trigger it.

@otavio
otavio merged commit b13d060 into master Jul 14, 2026
25 checks passed
@otavio
otavio deleted the feat/mfa-inapp-reset-disable branch July 14, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants