export async function POST()

in src/app/api/v1/fxa-rp-events/route.ts [122:455]


export async function POST(request: NextRequest) {
  let decodedJWT: JwtPayload;

  try {
    decodedJWT = (await authenticateFxaJWT(request)) as JwtPayload;
  } catch (e) {
    logger.error("fxa_rp_event", { exception: e as string });
    captureException(e);
    return NextResponse.json({ success: false }, { status: 401 });
  }

  if (!decodedJWT?.events) {
    // capture an exception in Sentry only. Throwing error will trigger FXA retry
    logger.error("fxa_rp_event", { decodedJWT });
    captureMessage(
      `fxa_rp_event: decodedJWT is missing attribute "events", ${
        decodedJWT as unknown as string
      }`,
    );
    return NextResponse.json(
      {
        success: false,
        message: 'fxa_rp_event: decodedJWT is missing attribute "events"',
      },
      { status: 400 },
    );
  }

  const fxaUserId = decodedJWT?.sub;
  if (!fxaUserId) {
    // capture an exception in Sentry only. Throwing error will trigger FXA retry
    captureMessage(
      `fxa_rp_event: decodedJWT is missing attribute "sub", ${
        decodedJWT as unknown as string
      }`,
    );
    return NextResponse.json(
      {
        success: false,
        message: 'fxa_rp_event: decodedJWT is missing attribute "sub"',
      },
      { status: 400 },
    );
  }

  const subscriber = await getSubscriberByFxaUid(fxaUserId);

  // highly unlikely, though it is a possible edge case from QA tests.
  // To reproduce, perform the following two actions in sequence very quickly in FxA settings portal:
  // 1. swap primary email and secondary email
  // 2. quickly follow step 1 with deleting the account
  // There's a chance that the fxa event from deletion gets to our service first, in which case, the user will be deleted from the db prior to the profile change event hitting our service
  if (!subscriber) {
    const e = new Error(
      `could not find subscriber with fxa user id: ${fxaUserId}`,
    );
    logger.error("fxa_rp_event", { exception: e.message });
    return NextResponse.json({ success: true, message: "OK" }, { status: 200 });
  }

  // reference example events: https://github.com/mozilla/fxa/blob/main/packages/fxa-event-broker/README.md
  for (const event in decodedJWT?.events) {
    switch (event) {
      case FXA_DELETE_USER_EVENT: {
        await deleteAccount(subscriber);
        break;
      }
      case FXA_PROFILE_CHANGE_EVENT: {
        const updatedProfileFromEvent = decodedJWT.events[
          event
        ] as ProfileChangeEvent;
        logger.info("fxa_profile_update", {
          subscriber_id: subscriber.id,
          event,
          updatedProfileFromEvent,
        });

        record("account", "profile_change", {
          string: {
            monitorUserId: subscriber.id.toString(),
          },
        });

        // get current profiledata
        // Typed as `any` because `subscriber` used to be typed as `any`, and
        // making that type more specific was enough work just by itself:
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const currentFxAProfile: any = subscriber?.fxa_profile_json;

        // merge new event into existing profile data
        if (Object.keys(updatedProfileFromEvent).length !== 0) {
          for (const key in updatedProfileFromEvent) {
            // primary email change
            if (key === "email") {
              await updatePrimaryEmail(
                subscriber,
                updatedProfileFromEvent[key as keyof ProfileChangeEvent] ||
                  subscriber.primary_email,
              );
            }
            if (currentFxAProfile && currentFxAProfile[key]) {
              currentFxAProfile[key] =
                updatedProfileFromEvent[key as keyof ProfileChangeEvent];
            }
          }
        }

        // update fxa profile data
        await updateFxAProfileData(subscriber, currentFxAProfile);
        break;
      }
      case FXA_PASSWORD_CHANGE_EVENT: {
        const updateFromEvent = decodedJWT.events[event];
        logger.info("fxa_password_change", {
          subscriber: subscriber.id,
          event,
          updateFromEvent,
        });

        record("account", "password_change", {
          string: {
            monitorUserId: subscriber.id.toString(),
          },
        });

        const refreshToken = subscriber.fxa_refresh_token ?? "";
        const accessToken = subscriber.fxa_access_token ?? "";
        if (!accessToken || !refreshToken) {
          logger.error("failed_changing_password", {
            subscriber_id: subscriber.id,
            fxa_refresh_token: refreshToken,
            fxa_access_token: accessToken,
          });
        }

        // MNTOR-1932: Change password should revoke sessions
        await revokeOAuthTokens({
          fxa_access_token: accessToken,
          fxa_refresh_token: refreshToken,
        });

        return NextResponse.json(
          { success: true, message: "session_revoked" },
          { status: 200 },
        );

        break;
      }
      case FXA_SUBSCRIPTION_CHANGE_EVENT: {
        const updatedSubscriptionFromEvent = decodedJWT.events[
          event
        ] as SubscriptionStateChangeEvent;
        logger.info("fxa_subscription_change", {
          subscriber: subscriber.id,
          event,
          updatedSubscriptionFromEvent,
        });

        try {
          // get profile id
          const oneRepProfileId = await getOnerepProfileId(subscriber.id);

          logger.info("get_onerep_profile", {
            subscriber_id: subscriber.id,
            oneRepProfileId,
          });

          const enabledFeatureFlags = await getEnabledFeatureFlags({
            isSignedOut: true,
          });

          if (
            updatedSubscriptionFromEvent.isActive &&
            updatedSubscriptionFromEvent.capabilities.includes(
              MONITOR_PREMIUM_CAPABILITY,
            )
          ) {
            // Update fxa profile data to match subscription status.
            // This is done before trying to activate the OneRep subscription, in case there are
            // any problems with activation.
            await changeSubscription(subscriber, true);

            // Set monthly monitor report value back to true
            await setMonthlyMonitorReport(subscriber, true);

            // MNTOR-2103: if one rep profile id doesn't exist in the db, fail immediately
            if (!oneRepProfileId) {
              logger.error("onerep_profile_not_found", {
                subscriber_id: subscriber.id,
              });

              captureMessage(
                `User subscribed but no OneRep profile Id found, user: ${
                  subscriber.id
                }\n
            Event: ${event}\n
            updateFromEvent: ${JSON.stringify(updatedSubscriptionFromEvent)}`,
              );

              return NextResponse.json(
                {
                  success: true,
                  message: "failed_activating_subscription_profile_id_missing",
                },
                { status: 200 },
              );
            }

            // activate and opt out profiles
            try {
              await activateProfile(oneRepProfileId);
            } catch (ex) {
              if (
                (ex as Error).message ===
                "Failed to activate OneRep profile: [403] [Forbidden]"
              )
                logger.error("profile_already_activated", {
                  subscriber_id: subscriber.id,
                  exception: ex,
                });
            }

            try {
              await optoutProfile(oneRepProfileId);
            } catch (ex) {
              if (
                (ex as Error).message ===
                "Failed to opt-out OneRep profile: [403] [Forbidden]"
              )
                logger.error("profile_already_opted_out", {
                  subscriber_id: subscriber.id,
                  exception: ex,
                });
            }

            logger.info("activated_onerep_profile", {
              subscriber_id: subscriber.id,
            });

            record("subscription", "activate", {
              string: {
                monitorUserId: subscriber.id.toString(),
              },
            });

            if (enabledFeatureFlags.includes("GA4SubscriptionEvents")) {
              await sendPingToGA(subscriber.id, "subscribe");
            }
          } else if (
            !updatedSubscriptionFromEvent.isActive &&
            updatedSubscriptionFromEvent.capabilities.includes(
              MONITOR_PREMIUM_CAPABILITY,
            )
          ) {
            // Update fxa profile data to match subscription status.
            // This is done before trying to deactivate the OneRep subscription, in case there are
            // any problems with deactivation.
            await changeSubscription(subscriber, false);

            // MNTOR-2103: if one rep profile id doesn't exist in the db, fail immediately
            if (!oneRepProfileId) {
              logger.error("onerep_profile_not_found", {
                subscriber_id: subscriber.id,
              });

              captureMessage(
                `No OneRep profile Id found, subscriber: ${subscriber.id}\n
                        Event: ${event}\n
                        updateFromEvent: ${JSON.stringify(
                          updatedSubscriptionFromEvent,
                        )}`,
              );
              return NextResponse.json(
                { success: true, message: "failed_deactivating_subscription" },
                { status: 200 },
              );
            }

            // deactivation stops opt out process
            try {
              await deactivateProfile(oneRepProfileId);
            } catch (ex) {
              if (
                (ex as Error).message ===
                "Failed to deactivate OneRep profile: [403] [Forbidden]"
              )
                logger.error("profile_already_opted_out", {
                  subscriber_id: subscriber.id,
                  exception: ex,
                });
            }

            logger.info("deactivated_onerep_profile", {
              subscriber_id: subscriber.id,
            });

            record("subscription", "cancel", {
              string: {
                monitorUserId: subscriber.id.toString(),
              },
            });

            if (enabledFeatureFlags.includes("GA4SubscriptionEvents")) {
              await sendPingToGA(subscriber.id, "unsubscribe");
            }
          }
        } catch (e) {
          captureMessage(
            `${(e as Error).message}\n
          Event: ${event}\n
          updateFromEvent: ${JSON.stringify(updatedSubscriptionFromEvent)}`,
          );
          logger.error("failed_activating_subscription", {
            subscriber_id: subscriber.id,
            message: (e as Error).message,
            stack: (e as Error).stack,
          });
          return NextResponse.json(
            { success: false, message: "failed_activating_subscription" },
            { status: 500 },
          );
        }
        break;
      }
      default:
        logger.warn("unhandled_event", {
          event,
        });
        break;
    }
  }

  return NextResponse.json({ success: true, message: "OK" }, { status: 200 });
}