Skip to content

feat(core,schemas,phrases,test): add submitStepUp with its audit key - #9582

Open
wangsijie wants to merge 6 commits into
wangsijie-log-14106-pure-step-up-route-allow-listfrom
wangsijie-log-14109-add-submitstepup-with-its-audit-log-key-and-pure-step-up
Open

feat(core,schemas,phrases,test): add submitStepUp with its audit key#9582
wangsijie wants to merge 6 commits into
wangsijie-log-14106-pure-step-up-route-allow-listfrom
wangsijie-log-14109-add-submitstepup-with-its-audit-log-key-and-pure-step-up

Conversation

@wangsijie

Copy link
Copy Markdown
Contributor

Summary

Stacked on #9580, which is stacked on #9575. Implements LOG-14109: the pure step-up completion path, the audit log key it writes, and the integration coverage that proves the flow end to end.

submitStepUp()

POST /experience/submit dispatches on the stored step-up mode. A pure step-up completes through submitStepUp() instead of a submit() with exemptions, so a guard or side effect added to submit() later never reaches step-up by default.

  • It asserts the achieved context satisfies selectedAcr, otherwise 403 session.step_up.acr_not_satisfied with no interaction result written. The achieved context is aggregated from the interaction's proofs on top of the context the session carried, so a submission that counted no verification derives nothing and fails there.
  • It writes only what the interaction established: newly bound MFA factors through toUserMfaVerifications(), and a newly established password or primary identifier from profile.data. Establishing is wired in a later milestone, so a step-up that only verifies existing methods runs no user write at all.
  • Nothing else from submit() runs: no captcha, no tenant MFA or profile policy, no passkey suggestion, no lastSignInAt, no SSO identity sync, no social / SSO token-set upserts, no JIT organization provisioning, no post-sign-in action, no data-hook context.
  • The login result carries the achieved acr / amr; achievedAcr may be stronger than selectedAcr.

Audit log

  • New Interaction.SignIn.StepUp.Submit key in the log-key grammar (packages/schemas/src/types/log/interaction.ts). Its payload records requestedAcrValues, selectedAcr, the achieved ACR and the proved factor families, plus the client id the experience middleware already prepends. A successful and a rejected submission each write one entry, with the outcome on the entry's result / error.
  • Credentials never reach the log, and the per-verification Interaction.SignIn.Verification.{type}.{action} keys are unchanged.
  • The submit operation's step-up 403 is documented through a Dev feature OpenAPI supplement.

Integration coverage

New step-up-interaction/submission.test.ts drives a developer-created OIDC application through a real Authorization Code Flow: 1fa via the pinned password and via the pinned primary email code, mfa via the enrolled factor alone on a 1fa session, a 1fa-only submission rejected when mfa was selected and then completed by the missing factor, max_age=0 forcing an active verification without forcing the password, the session context being replaced rather than merged, no lastSignInAt write and no tenant MFA policy, a mid-flow refetch, and the audit entries for both outcomes.

Testing

Unit tests

The new integration suite needs a running Logto and Postgres, so CI is its first execution.

@github-actions github-actions Bot added the feature Cool stuff label Sep 10, 2026
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

COMPARE TO master

Total Size Diff ⚠️ 📈 +62.14 KB

Diff by File
Name Diff
packages/console/src/consts/logs.ts 📈 +71 Bytes
packages/core/src/routes/experience/classes/experience-interaction.test.ts 📈 +9.05 KB
packages/core/src/routes/experience/classes/experience-interaction.ts 📈 +5.93 KB
packages/core/src/routes/experience/experience.step-up.openapi.json 📈 +1.03 KB
packages/core/src/routes/experience/index.test.ts 📈 +5.88 KB
packages/core/src/routes/experience/index.ts 📈 +490 Bytes
packages/core/src/routes/experience/middleware/koa-step-up-route-guard.test.ts 📈 +9 KB
packages/core/src/routes/experience/middleware/koa-step-up-route-guard.ts 📈 +2.37 KB
packages/core/src/routes/experience/verification-routes/password-verification.test.ts 📈 +814 Bytes
packages/core/src/routes/experience/verification-routes/password-verification.ts 📈 +227 Bytes
packages/core/src/routes/experience/verification-routes/verification-code.ts 📈 +616 Bytes
packages/integration-tests/src/client/index.ts 📈 +1.22 KB
packages/integration-tests/src/helpers/experience/authorization.ts 📈 +163 Bytes
packages/integration-tests/src/tests/api/experience-api/step-up-interaction/routes.test.ts 📈 +4.5 KB
packages/integration-tests/src/tests/api/experience-api/step-up-interaction/subject-verification.test.ts 📈 +240 Bytes
packages/integration-tests/src/tests/api/experience-api/step-up-interaction/submission.test.ts ⚠️ 📈 +15.4 KB
packages/integration-tests/src/tests/api/oidc/acr-interaction-policy.test.ts 📈 +118 Bytes
packages/phrases/src/locales/ar/errors/session.ts 📈 +266 Bytes
packages/phrases/src/locales/de/errors/session.ts 📈 +260 Bytes
packages/phrases/src/locales/en/errors/session.ts 📈 +225 Bytes
packages/phrases/src/locales/es/errors/session.ts 📈 +229 Bytes
packages/phrases/src/locales/fa-ir/errors/session.ts 📈 +307 Bytes
packages/phrases/src/locales/fr/errors/session.ts 📈 +253 Bytes
packages/phrases/src/locales/it/errors/session.ts 📈 +224 Bytes
packages/phrases/src/locales/ja/errors/session.ts 📈 +271 Bytes
packages/phrases/src/locales/ko/errors/session.ts 📈 +242 Bytes
packages/phrases/src/locales/pl-pl/errors/session.ts 📈 +219 Bytes
packages/phrases/src/locales/pt-br/errors/session.ts 📈 +224 Bytes
packages/phrases/src/locales/pt-pt/errors/session.ts 📈 +225 Bytes
packages/phrases/src/locales/ru/errors/session.ts 📈 +371 Bytes
packages/phrases/src/locales/th/errors/session.ts 📈 +485 Bytes
packages/phrases/src/locales/tr-tr/errors/session.ts 📈 +245 Bytes
packages/phrases/src/locales/zh-cn/errors/session.ts 📈 +175 Bytes
packages/phrases/src/locales/zh-hk/errors/session.ts 📈 +172 Bytes
packages/phrases/src/locales/zh-tw/errors/session.ts 📈 +172 Bytes
packages/schemas/src/types/log/interaction.ts 📈 +663 Bytes

@github-actions github-actions Bot added size/xl and removed size/xl labels Sep 11, 2026
@wangsijie
wangsijie marked this pull request as ready for review September 11, 2026 03:12
@wangsijie
wangsijie force-pushed the wangsijie-log-14106-pure-step-up-route-allow-list branch from af42e8b to e2f75b7 Compare September 11, 2026 03:30
@wangsijie
wangsijie force-pushed the wangsijie-log-14109-add-submitstepup-with-its-audit-log-key-and-pure-step-up branch from d267c37 to ea8c25b Compare September 11, 2026 03:34
@github-actions github-actions Bot added size/xl and removed size/xl labels Sep 11, 2026
@simeng-li
simeng-li requested a balanced review from Copilot September 11, 2026 07:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The audit-key type permits invalid non-sign-in keys, and the audit integration assertion can pass using logs from earlier tests.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds the pure step-up completion flow, dedicated audit logging, localized errors, API documentation, and end-to-end coverage.

Changes:

  • Implements submitStepUp() with ACR validation and limited account persistence.
  • Adds the step-up audit key and console label.
  • Adds localized errors and integration/unit coverage.
File summaries
File Description
packages/schemas/src/types/log/interaction.ts Adds the step-up audit key grammar.
packages/phrases/src/locales/zh-tw/errors/session.ts Adds the Traditional Chinese error.
packages/phrases/src/locales/zh-hk/errors/session.ts Adds the Hong Kong Chinese error.
packages/phrases/src/locales/zh-cn/errors/session.ts Adds the Simplified Chinese error.
packages/phrases/src/locales/tr-tr/errors/session.ts Adds the Turkish error.
packages/phrases/src/locales/th/errors/session.ts Adds the Thai error.
packages/phrases/src/locales/ru/errors/session.ts Adds the Russian error.
packages/phrases/src/locales/pt-pt/errors/session.ts Adds the Portuguese error.
packages/phrases/src/locales/pt-br/errors/session.ts Adds the Brazilian Portuguese error.
packages/phrases/src/locales/pl-pl/errors/session.ts Adds the Polish error.
packages/phrases/src/locales/ko/errors/session.ts Adds the Korean error.
packages/phrases/src/locales/ja/errors/session.ts Adds the Japanese error.
packages/phrases/src/locales/it/errors/session.ts Adds the Italian error.
packages/phrases/src/locales/fr/errors/session.ts Adds the French error.
packages/phrases/src/locales/fa-ir/errors/session.ts Adds the Persian error.
packages/phrases/src/locales/es/errors/session.ts Adds the Spanish error.
packages/phrases/src/locales/en/errors/session.ts Adds the English error.
packages/phrases/src/locales/de/errors/session.ts Adds the German error.
packages/phrases/src/locales/ar/errors/session.ts Adds the Arabic error.
packages/integration-tests/src/tests/api/experience-api/step-up-interaction/submission.test.ts Covers step-up submission end to end.
packages/integration-tests/src/helpers/experience/authorization.ts Supports custom authorization redirect URIs.
packages/integration-tests/src/client/index.ts Adds resumed authorization handling.
packages/core/src/routes/experience/index.ts Dispatches step-up submissions and audit keys.
packages/core/src/routes/experience/index.test.ts Tests route dispatch and audit selection.
packages/core/src/routes/experience/experience.step-up.openapi.json Documents the dev-feature response.
packages/core/src/routes/experience/classes/experience-interaction.ts Implements pure step-up completion.
packages/core/src/routes/experience/classes/experience-interaction.test.ts Tests step-up completion behavior.
packages/console/src/consts/logs.ts Adds the audit-log display title.
Review details
  • Files reviewed: 28/28 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/schemas/src/types/log/interaction.ts
- Dispatch `POST /experience/submit` on the stored step-up mode: a pure step-up
  completes through `submitStepUp()`, an allow-list path that asserts the achieved
  context satisfies `selectedAcr`, writes only the methods the interaction
  established, and runs no sign-in guard or side effect (`lastSignInAt`, tenant MFA
  policy, hooks, identity and token-set sync, JIT provisioning are all skipped).
- Add `Interaction.SignIn.StepUp.Submit` to the log-key grammar, recording the
  requested and achieved classes and the proved factor families, and document the
  operation through a dev-feature OpenAPI supplement.
- Add unit coverage for the completion path, the persistence scope and the audit
  payload, plus pure step-up integration coverage driven through a real
  Authorization Code Flow on a developer-created application.
- `manualConsent()` follows the resumed authorization directly; probing the
  interaction URL first consumed the authorization code, so the follow-up request
  had nothing left to complete.
- Give the TOTP user `skipMfaOnSignIn`, so the password sign-in that establishes
  the session is not challenged for MFA; the step-up still verifies the factor
  because the requested ACR is what asks for it.
- Add `MockClient.resumeAuthorization()`: a resumed authorization either asks for
  consent once more or goes straight back to the callback, and both are completed
  from a single request. Re-requesting a URL that already handed back a callback
  consumes the authorization a second time, which is what broke the previous fix.
- Enroll a separate TOTP user per test: a TOTP code is single-use within its time
  step, so tests sharing one secret failed the second verification.
- Restore the user-controlled MFA settings after the policy test, so the tests
  that follow still have TOTP enabled.
…heck

The submit route appends the interaction snapshot to its audit entry, exactly as
it does for a sign-in, so the entry names the verification records the interaction
holds. Those are identifiers, not credentials; the credentials check stays.
`persistEstablishedMethods()` writes the identifier or factor the interaction
established without the revalidation `submit()` performs, so a duplicate primary
email / phone would reach the `(tenant_id, primary_email)` unique constraint as a
raw error instead of the 422 the API promises, and a factor disabled in between
would be bound anyway.

Both guards only run when there is something to write, which the step-up route
allow-list keeps unreachable until the establishment and enrollment routes open.
The ACR assertion runs before the subject is read, so a submission that counted no
verification is a 403, not the 404 the JSDoc promised. Keep the 404 documented for
the shape that does reach it — a counted `bind` proof, which identifies nobody and
which the allow-list blocks until establishment opens — and pin that shape with a
test instead of deleting the guard.
@wangsijie
wangsijie force-pushed the wangsijie-log-14109-add-submitstepup-with-its-audit-log-key-and-pure-step-up branch from ea8c25b to c097cc6 Compare September 11, 2026 09:20
@github-actions github-actions Bot added size/xl and removed size/xl labels Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants