Repository: logto-io/logto
File: packages/core/src/routes/experience/classes/libraries/sign-in-experience-validator.ts (TypeScript)

Code before:
  ) {}

  /**
   * @throws {RequestError} with status 403 if the interaction event is not allowed
   */
  public async guardInteractionEvent(
    event: InteractionEvent,
    verificationRecord?: VerificationRecord
  ) {
    const { signInMode } = await this.getSignInExperienceData();
    const { type, isVerified } = verificationRecord ?? {};

    switch (event) {
      case InteractionEvent.SignIn: {
        assertThat(
          signInMode !== SignInMode.Register,
          new RequestError({ code: 'auth.forbidden', status: 403 })
        );
        break;
      }
      case InteractionEvent.Register: {
        assertThat(
          signInMode !== SignInMode.SignIn ||
            // This guarantees new users can still be created through one-time token
            // authentication even if the registration is turned off.
            (type === VerificationType.OneTimeToken && isVerified),
          new RequestError({ code: 'auth.forbidden', status: 403 })
        );
        break;
      }
      case InteractionEvent.ForgotPassword: {
        break;
      }
    }
  }

  public async guardIdentificationMethod(
    event: InteractionEvent.ForgotPassword | InteractionEvent.SignIn,
    verificationRecord: VerificationRecord
  ) {
    await this.guardInteractionEvent(event, verificationRecord);

    switch (event) {
      case InteractionEvent.SignIn: {
        await this.guardSignInVerificationMethod(verificationRecord);
        break;
      }
      case InteractionEvent.ForgotPassword: {
        this.guardForgotPasswordVerificationMethod(verificationRecord);
        break;
      }

Review comment (suggestion):
See the comments above.  Maybe declaring an exclusive `isOnetimeTokenVerified` parameter is better than allowing any verification record as an input.

Code after:
  ) {}

  /**
   * @param event - The interaction event to guard
   * @param hasVerifiedOneTimeToken - Whether there is a verified one-time token verification record
   * @throws {RequestError} with status 403 if the interaction event is not allowed
   */
  public async guardInteractionEvent(event: InteractionEvent, hasVerifiedOneTimeToken = false) {
    const { signInMode } = await this.getSignInExperienceData();

    switch (event) {
      case InteractionEvent.SignIn: {
        assertThat(
          signInMode !== SignInMode.Register,
          new RequestError({ code: 'auth.forbidden', status: 403 })
        );
        break;
      }
      case InteractionEvent.Register: {
        assertThat(
          signInMode !== SignInMode.SignIn ||
            // This guarantees new users can still be created through one-time token
            // authentication even if the registration is turned off.
            hasVerifiedOneTimeToken,
          new RequestError({ code: 'auth.forbidden', status: 403 })
        );
        break;
      }
      case InteractionEvent.ForgotPassword: {
        break;
      }
    }
  }

  public async guardIdentificationMethod(
    event: InteractionEvent.ForgotPassword | InteractionEvent.SignIn,
    verificationRecord: VerificationRecord
  ) {
    const hasVerifiedOneTimeToken =
      verificationRecord.type === VerificationType.OneTimeToken && verificationRecord.isVerified;
    await this.guardInteractionEvent(event, hasVerifiedOneTimeToken);

    switch (event) {
      case InteractionEvent.SignIn: {
        await this.guardSignInVerificationMethod(verificationRecord);
        break;
      }
      case InteractionEvent.ForgotPassword: {
        this.guardForgotPasswordVerificationMethod(verificationRecord);
        break;
      }