fix(core): stabilize adaptive MFA hook delivery#8448
fix(core): stabilize adaptive MFA hook delivery#8448
Conversation
COMPARE TO
|
| Name | Diff |
|---|---|
| packages/core/src/routes/experience/classes/experience-interaction.adaptive-mfa.test.ts | 📈 +5.65 KB |
| packages/core/src/routes/experience/classes/experience-interaction.ts | 📈 +1023 Bytes |
| packages/core/src/routes/experience/middleware/interaction-hook-dispatch.ts | 📈 +930 Bytes |
| packages/core/src/routes/experience/middleware/koa-experience-interaction-hooks.test.ts | 📈 +5.88 KB |
| packages/core/src/routes/experience/middleware/koa-experience-interaction-hooks.ts | 📈 +1.54 KB |
| packages/core/src/routes/experience/types.ts | 📈 +177 Bytes |
| packages/integration-tests/src/tests/api/hook/hook.trigger.experience.adaptive-mfa.test.ts | 📈 +4.18 KB |
| packages/integration-tests/src/tests/api/hook/hook.trigger.experience.test.ts | 📈 +2.63 KB |
| packages/integration-tests/src/tests/api/hook/utils.test.ts | 📈 +1.15 KB |
| packages/integration-tests/src/tests/api/hook/utils.ts | 📈 +882 Bytes |
bd1e666 to
6563093
Compare
6563093 to
6c10345
Compare
There was a problem hiding this comment.
Pull request overview
Stabilizes delivery semantics for the adaptive MFA interaction hook so the webhook is still emitted when an MFA challenge causes /experience/submit to fail with session.mfa.require_mfa_verification, while preventing duplicate adaptive MFA hook deliveries across submit retries within the same interaction.
Changes:
- Dispatch interaction hooks from the experience interaction-hook middleware
finallyblock (keeping data hooks success-only). - Only enqueue
PostSignInAdaptiveMfaTriggeredwhen MFA is required and not yet verified for the current interaction. - Add unit + integration regressions covering the failed-submit adaptive MFA path and ensuring
PostSignInremains success-only across MFA challenge flows.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/integration-tests/src/tests/api/hook/utils.ts | Adds polling helpers for webhook log fetching; updates negative/positive assertions behavior. |
| packages/integration-tests/src/tests/api/hook/hook.trigger.experience.test.ts | Adds integration regression ensuring PostSignIn is emitted only after MFA challenge completion. |
| packages/integration-tests/src/tests/api/hook/hook.trigger.experience.adaptive-mfa.test.ts | Adds integration regression for failed-submit adaptive MFA hook emission + no duplicates + PostSignIn success-only. |
| packages/core/src/routes/experience/middleware/koa-experience-interaction-hooks.ts | Moves interaction hook dispatch into finally; keeps data hook dispatch success-only. |
| packages/core/src/routes/experience/middleware/koa-experience-interaction-hooks.test.ts | Adds unit coverage verifying interaction hooks are dispatched on error when results were assigned. |
| packages/core/src/routes/experience/classes/experience-interaction.ts | Prevents adaptive MFA hook enqueueing once MFA is already verified in the current interaction. |
| packages/core/src/routes/experience/classes/experience-interaction.adaptive-mfa.test.ts | Adds unit regression ensuring no adaptive MFA hook is assigned when MFA is already satisfied. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
packages/core/src/routes/experience/classes/experience-interaction.adaptive-mfa.test.ts
Outdated
Show resolved
Hide resolved
| const logEntry = assertions.toBeUndefined | ||
| ? await getLatestHookLogAfterDelay(hookId, event) | ||
| : await waitForLatestHookLog(hookId, event); | ||
|
|
There was a problem hiding this comment.
assertHookLogResult uses only a single 100ms delay before asserting toBeUndefined. Since webhook delivery is async, this can produce false passes if the hook arrives slightly later (especially on slower CI), making negative assertions flaky. Consider polling for a bounded window (similar to waitForLatestHookLog) and only then asserting undefined, or otherwise ensuring the test observes a sufficient “no log” period.
packages/core/src/routes/experience/classes/experience-interaction.adaptive-mfa.test.ts
Outdated
Show resolved
Hide resolved
| if (interactionHookContext.interactionHookResults.length > 0) { | ||
| // Interaction hooks are queued when the corresponding interaction event is known to | ||
| // have happened. `PostSignInAdaptiveMfaTriggered` is assigned before the MFA | ||
| // challenge error is thrown, so dispatch must happen in `finally`. | ||
| // If future interaction hooks need mixed semantics, split them by dispatch policy | ||
| // (for example, success-only vs finally) instead of sending every interaction hook | ||
| // on failed submits. | ||
| // Hooks should not crash the app | ||
| void trySafe( | ||
| triggerInteractionHooks(getConsoleLogFromContext(ctx), interactionHookContext) | ||
| ); | ||
| } |
There was a problem hiding this comment.
This is a breaking change? triggerInteractionHooks includes PostSignIn/PostRegister events, which should only triggered if the interaction submit succesfully.
There was a problem hiding this comment.
Fixed.
I split the experience interaction hook dispatch into two paths:
- success-only interaction hooks are only released after
next()completes successfully PostSignInAdaptiveMfaTriggeredis still released fromfinally, so it is emitted when MFA challenge submission throwssession.mfa.require_mfa_verification
This keeps the adaptive MFA webhook behavior while preventing default interaction hooks like PostSignIn from being sent on failed submits.
I also extracted the dispatch helpers into interaction-hook-dispatch.ts to keep the middleware logic focused, and added middleware coverage for both cases:
- failed submit only releases the adaptive MFA hook
- successful submit releases the success-only hooks separately from the release-anyway hook
6c10345 to
b92100b
Compare
09e1f0b to
36393c0
Compare
Adaptive MFA assigns the interaction hook result before MFA verification challenge errors are thrown, but the experience interaction middleware only dispatched interaction hooks after successful requests. That made PostSignInAdaptiveMfaTriggered disappear when the first submit returned session.mfa.require_mfa_verification. Dispatch interaction hooks in the middleware finally block so assigned interaction events are preserved for adaptive MFA challenge flows. Add unit and integration coverage for the failed submit path.
Run the repository lint fix pass after the adaptive MFA hook change. This picks up the import ordering change in the new integration test so the branch stays clean before push.
Document why data hooks remain success-only while interaction hooks dispatch from the experience middleware finally block. Note that future interaction hooks may need explicit success-only vs finally dispatch policies if their semantics diverge from adaptive MFA.
Dispatching interaction hooks from the experience middleware finally block made adaptive MFA webhooks visible on failed submit requests, but it also exposed that the same sign-in flow could enqueue PostSignInAdaptiveMfaTriggered again after the MFA challenge succeeded. Only emit the adaptive MFA interaction hook while the current interaction still requires MFA verification, and add regression coverage to keep PostSignIn success-only in MFA challenge flows.
CI failed in the integration-tests lint job because the new hook regression coverage introduced import-order and prettier violations. Only fix the lint errors in the new hook regression tests so the behavioral changes stay unchanged.
67eac9f to
cf11f1e
Compare
Summary
PostSignInAdaptiveMfaTriggeredfrom the experience interaction hook middlewarefinallyblock so adaptive MFA challenge submits still emit the webhook when/experience/submitreturnssession.mfa.require_mfa_verificationPostSignInremains success-only in experience MFA challenge flowsTesting
Integration tests
Checklist
.changeset