Repository: logto-io/logto
File: packages/core/src/routes/experience/verification-routes/verification-code-helpers.ts (TypeScript)

Code before:
  const { experienceInteraction } = ctx;

  // Create audit log
  const log = createVerificationCodeAuditLog(ctx, experienceInteraction, identifier, Action.Submit);

  log.append({
    payload: {
      identifier,
      verificationId,
      code,
    },
  });

  // Get verification record - the type system ensures this is a code verification record
  // because verificationType is constrained to code verification types
  const codeVerificationRecord = experienceInteraction.getVerificationRecordByTypeAndId(
    verificationType,
    verificationId
  );

  // Verify with sentinel protection
  // The verify method is guaranteed to exist because verificationType is constrained
  // to code verification types (Email, Phone, MfaEmail, MfaPhone)
  const isMfaVerification =
    verificationType === VerificationType.MfaEmailVerificationCode ||
    verificationType === VerificationType.MfaPhoneVerificationCode;
  const useDevFeatures = EnvSet.values.isDevFeaturesEnabled;
  const sentinelAction =
    useDevFeatures && isMfaVerification
      ? SentinelActivityAction.Mfa
      : SentinelActivityAction.VerificationCode;
  const identifierForSentinel =
    useDevFeatures && isMfaVerification && sentinelIdentifier ? sentinelIdentifier : identifier;

  await withSentinel(
    {
      ctx,
      sentinel,
      action: sentinelAction,
      identifier: identifierForSentinel,
      payload: {
        event: experienceInteraction.interactionEvent,
        verificationId: codeVerificationRecord.id,
      },
    },
    codeVerificationRecord.verify(identifier, code)
  );

  // Save state
  await experienceInteraction.save();


Review comment (suggestion):
I doubt whether we should split the sentinel policy here.  Regardless of whether the verification code is used for MFA or email verification, it should share the same sentinel action, since the target is the same user account.

Code after:

  log.append({
    payload: {
      identifier,
      verificationId,
      code,
    },
  });

  // Get verification record - the type system ensures this is a code verification record
  // because verificationType is constrained to code verification types
  const codeVerificationRecord = experienceInteraction.getVerificationRecordByTypeAndId(
    verificationType,
    verificationId
  );

  // Verify with sentinel protection
  // The verify method is guaranteed to exist because verificationType is constrained
  // to code verification types (Email, Phone, MfaEmail, MfaPhone)
  await withSentinel(
    {
      ctx,
      sentinel,
      action: SentinelActivityAction.VerificationCode,
      identifier,
      payload: {
        event: experienceInteraction.interactionEvent,
        verificationId: codeVerificationRecord.id,
      },
    },
    codeVerificationRecord.verify(identifier, code)
  );

  // Save state
  await experienceInteraction.save();

  return {
    verificationId: codeVerificationRecord.id,
  };
};

type GetMfaIdentifierParams = {
  identifierType: SignInIdentifier.Email | SignInIdentifier.Phone;
  experienceInteraction: ExperienceInteraction;
  queries: Queries;
};

/**
 * Helper to get MFA identifier from user profile
 * @internal
 */