diff --git a/.changeset/nine-turtles-learn.md b/.changeset/nine-turtles-learn.md new file mode 100644 index 000000000..fe141d1fa --- /dev/null +++ b/.changeset/nine-turtles-learn.md @@ -0,0 +1,29 @@ +--- +"@logto/experience": patch +"@logto/core": patch +--- + +fix the new user from SSO register hook event not triggering bug + +### Issue + +When a new user registers via SSO, the `PostRegister` interaction hook event is not triggered. `PostSignIn` event is mistakenly triggered instead. + +### Root Cause + +In the SSO `post /api/interaction/sso/:connectionId/registration` API, we update the interaction event to `Register`. +However, the hook middleware reads the event from interaction session ahead of the API logic, and the event is not updated resulting in the wrong event being triggered. + +In the current interaction API design, we should mutate the interaction event by calling the `PUT /api/interaction/event` API, instead of updating the event directly in the submit interaction APIs. (Just like the no direct mutation rule for a react state). So we can ensure the correct side effect like logs and hooks are triggered properly. + +All the other sign-in methods are using the `PUT /api/interaction/event` API to update the event. But when implementing the SSO registration API, we were trying to reduce the API requests and directly updated the event in the registration API which will submit the interaction directly. + +### Solution + +Remove the event update logic in the SSO registration API and call the `PUT /api/interaction/event` API to update the event. +This will ensure the correct event is triggered in the hook middleware. + +### Action Items + +Align the current interaction API design for now. +Need to improve the session/interaction API logic to simplify the whole process. diff --git a/packages/core/src/routes/interaction/single-sign-on.ts b/packages/core/src/routes/interaction/single-sign-on.ts index 99faf43e1..e02dac160 100644 --- a/packages/core/src/routes/interaction/single-sign-on.ts +++ b/packages/core/src/routes/interaction/single-sign-on.ts @@ -18,8 +18,8 @@ import { getInteractionStorage, storeInteractionResult } from './utils/interacti import { getSingleSignOnAuthenticationResult } from './utils/single-sign-on-session.js'; import { authorizationUrlPayloadGuard, - getSsoAuthorizationUrl, getSsoAuthentication, + getSsoAuthorizationUrl, handleSsoAuthentication, registerWithSsoAuthentication, } from './utils/single-sign-on.js'; @@ -134,7 +134,6 @@ export default function singleSignOnRoutes( koaInteractionHooks(libraries), async (ctx, next) => { const { - createLog, assignInteractionHookResult, guard: { params }, } = ctx; @@ -147,13 +146,6 @@ export default function singleSignOnRoutes( new RequestError({ code: 'auth.forbidden', status: 403 }) ); - const registerEventUpdateLog = createLog(`Interaction.Register.Update`); - registerEventUpdateLog.append({ event: 'register' }); - - // Update the interaction session event to register if no related user account found. - // Set the merge flag to true to merge the register event with the existing sso interaction session - await storeInteractionResult({ event: InteractionEvent.Register }, ctx, provider, true); - // Throw 404 if no related session found const authenticationResult = await getSingleSignOnAuthenticationResult( ctx, diff --git a/packages/experience/src/apis/interaction.ts b/packages/experience/src/apis/interaction.ts index 1763ce40f..35470c2db 100644 --- a/packages/experience/src/apis/interaction.ts +++ b/packages/experience/src/apis/interaction.ts @@ -2,22 +2,23 @@ import { InteractionEvent, - type SignInIdentifier, + type BindMfaPayload, type EmailVerificationCodePayload, type PhoneVerificationCodePayload, + type SignInIdentifier, type SocialConnectorPayload, type SocialEmailPayload, type SocialPhonePayload, - type BindMfaPayload, type VerifyMfaPayload, - type WebAuthnRegistrationOptions, type WebAuthnAuthenticationOptions, + type WebAuthnRegistrationOptions, } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import api from './api'; -const interactionPrefix = '/api/interaction'; +export const interactionPrefix = '/api/interaction'; + const verificationPath = `verification`; type Response = { diff --git a/packages/experience/src/apis/single-sign-on.ts b/packages/experience/src/apis/single-sign-on.ts index d8a2eab28..12d5c80e2 100644 --- a/packages/experience/src/apis/single-sign-on.ts +++ b/packages/experience/src/apis/single-sign-on.ts @@ -1,6 +1,9 @@ -import api from './api'; +import { InteractionEvent } from '@logto/schemas'; -const ssoPrefix = '/api/interaction/single-sign-on'; +import api from './api'; +import { interactionPrefix } from './interaction'; + +const ssoPrefix = `${interactionPrefix}/single-sign-on`; type Response = { redirectTo: string; @@ -39,5 +42,12 @@ export const singleSignOnAuthorization = async (connectorId: string, payload: un }) .json(); -export const singleSignOnRegistration = async (connectorId: string) => - api.post(`${ssoPrefix}/${connectorId}/registration`).json(); +export const singleSignOnRegistration = async (connectorId: string) => { + await api.put(`${interactionPrefix}/event`, { + json: { + event: InteractionEvent.Register, + }, + }); + + return api.post(`${ssoPrefix}/${connectorId}/registration`).json(); +};