diff --git a/locales-pending/premium.ftl b/locales-pending/premium.ftl
index 73dda9884..cbfce53af 100644
--- a/locales-pending/premium.ftl
+++ b/locales-pending/premium.ftl
@@ -517,3 +517,6 @@ leaked-security-questions-steps-subtitle = This requires access to your account,
# $breach_name is the name of the breach where the security questions were found.
leaked-security-questions-step-one = Update your security questions on { $breach_name }.
leaked-security-questions-step-two = Update them on any other site where you used the same security questions. Be sure to use different security questions for every account.
+
+# Subscription
+subscription-check-loading = Loading, please wait…
diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/data-broker-profiles/welcome-to-premium/page.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/data-broker-profiles/welcome-to-premium/page.tsx
index 0700dc90e..6b9e31334 100644
--- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/data-broker-profiles/welcome-to-premium/page.tsx
+++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/fix/data-broker-profiles/welcome-to-premium/page.tsx
@@ -16,6 +16,7 @@ import {
getExposureReduction,
} from "../../../../../../../../functions/server/dashboard";
import { Button } from "../../../../../../../../components/server/Button";
+import { hasPremium } from "../../../../../../../../functions/universal/user";
export default async function WelcomeToPremium() {
const l10n = getL10n();
@@ -25,6 +26,11 @@ export default async function WelcomeToPremium() {
redirect("/redesign/user/dashboard/");
}
+ // The user may have subscribed and just need their session updated - they will be redirected back to try again if it looks valid.
+ if (!hasPremium(session.user)) {
+ redirect(`${process.env.NEXTAUTH_URL}/redesign/user/dashboard/subscribed`);
+ }
+
const result = await getOnerepProfileId(session.user.subscriber.id);
const profileId = result[0]["onerep_profile_id"] ?? -1;
const scanResultItems =
diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/subscribed/page.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/subscribed/page.tsx
index ddc00f3ae..f03ab4bd2 100644
--- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/subscribed/page.tsx
+++ b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/subscribed/page.tsx
@@ -2,77 +2,50 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { getServerSession } from "next-auth";
-import { getOnerepProfileId } from "../../../../../../../db/tables/subscribers";
-import { authOptions } from "../../../../../../api/utils/auth";
-import {
- activateProfile,
- getAllScanResults,
- isEligibleForPremium,
- optoutProfile,
-} from "../../../../../../functions/server/onerep";
-import {
- getLatestOnerepScanResults,
- addOnerepScanResults,
-} from "../../../../../../../db/tables/onerep_scans";
-import { getCountryCode } from "../../../../../../functions/server/getCountryCode";
-import { headers } from "next/headers";
+"use client";
-export default async function Subscribed() {
- const session = await getServerSession(authOptions);
- if (!session?.user?.subscriber) {
- throw new Error("No session");
- }
+import { useSession } from "next-auth/react";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
- if (!(await isEligibleForPremium(session.user, getCountryCode(headers())))) {
- throw new Error("Not eligible for premium");
- }
+import { hasPremium } from "../../../../../../functions/universal/user";
+import { captureException } from "@sentry/browser";
+import { useL10n } from "../../../../../../hooks/l10n";
- const result = await getOnerepProfileId(session.user.subscriber.id);
- const profileId = result[0]["onerep_profile_id"] as number;
+/**
+ * Client-side page to update session info.
+ *
+ * Next-Auth does not have a simple way to do this purely from the server-side, so we
+ * use this page to check and redirect appropriately.
+ *
+ * NOTE: this does not replace doing server-side `hasPremium` checks! This is just
+ * a convenience so users do not need to sign out and back in to refresh their session
+ * after subscribing.
+ */
+export default function Subscribed() {
+ const l10n = useL10n();
+ const { update } = useSession();
+ const router = useRouter();
- try {
- await activateProfile(profileId);
- await optoutProfile(profileId);
- } catch (ex) {
- console.debug(ex); // TODO handle
- }
+ useEffect(() => {
+ async function updateSession() {
+ try {
+ const result = await update();
+ if (hasPremium(result?.user)) {
+ router.replace(
+ `/redesign/user/dashboard/fix/data-broker-profiles/welcome-to-premium`
+ );
+ } else {
+ router.replace(`/`);
+ }
+ } catch (ex) {
+ console.error(ex);
+ captureException(ex);
+ router.replace(`/`);
+ }
+ }
+ void updateSession();
+ }, [update, router]);
- const dev =
- process.env.NODE_ENV === "development" || process.env.APP_ENV === "heroku";
- const latestScan = await getLatestOnerepScanResults(profileId);
- if (!latestScan.scan) {
- throw new Error("Must have performed manual scan");
- }
-
- const scans = await getAllScanResults(profileId);
-
- // In dev mode, record scans every time this page is reloaded.
- // The webhook does this in production.
- if (dev) {
- await addOnerepScanResults(
- profileId,
- latestScan.scan.onerep_scan_id,
- scans,
- "initial",
- latestScan.scan.onerep_scan_status
- );
- }
-
- return (
-
-
-
You are now subscribed
- {dev ? (
-
-
Dev mode enabled
-
Reload this page to update scan
-
{JSON.stringify(scans, null, 2)}
-
- ) : (
- ""
- )}
-
-
- );
+ return {l10n.getString("subscription-check-loading")}
;
}
diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/unsubscribed/page.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/unsubscribed/page.tsx
deleted file mode 100644
index 6259d83a4..000000000
--- a/src/app/(proper_react)/redesign/(authenticated)/user/dashboard/unsubscribed/page.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { getServerSession } from "next-auth";
-import { getOnerepProfileId } from "../../../../../../../db/tables/subscribers";
-import { authOptions } from "../../../../../../api/utils/auth";
-import {
- deactivateProfile,
- isEligibleForPremium,
-} from "../../../../../../functions/server/onerep";
-import { getCountryCode } from "../../../../../../functions/server/getCountryCode";
-import { headers } from "next/headers";
-
-export default async function Unsubscribed() {
- const session = await getServerSession(authOptions);
- if (!session?.user?.subscriber) {
- throw new Error("No session");
- }
-
- if (!(await isEligibleForPremium(session.user, getCountryCode(headers())))) {
- throw new Error("Not eligible for premium");
- }
-
- const result = await getOnerepProfileId(session.user.subscriber.id);
- const profileId = result[0]["onerep_profile_id"] as number;
-
- try {
- await deactivateProfile(profileId);
- } catch (ex) {
- console.debug(ex); // TODO handle
- }
-
- return (
-
-
-
You are now unsubscribed
-
-
- );
-}
diff --git a/src/app/(proper_react)/redesign/(authenticated)/user/welcome/remove/page.tsx b/src/app/(proper_react)/redesign/(authenticated)/user/welcome/remove/page.tsx
deleted file mode 100644
index 14d751881..000000000
--- a/src/app/(proper_react)/redesign/(authenticated)/user/welcome/remove/page.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { getOnerepProfileId } from "../../../../../../../db/tables/subscribers";
-import { getServerSession } from "next-auth";
-import { authOptions } from "../../../../../../api/utils/auth";
-import {
- activateProfile,
- optoutProfile,
-} from "../../../../../../functions/server/onerep";
-import Script from "next/script";
-import { isUserSubscribed } from "../../../../../../functions/server/isUserSubscribed";
-
-export function generateMetadata() {
- return {
- title: "Welcome - Results",
- };
-}
-
-export default async function UserWelcomeRemove() {
- const session = await getServerSession(authOptions);
- if (!session?.user?.subscriber?.id) {
- throw new Error("No session");
- }
-
- if (!(await isUserSubscribed())) {
- return (
-
- );
- }
-
- const result = await getOnerepProfileId(session.user.subscriber.id);
- const profileId = result[0]["onerep_profile_id"] as number;
- await activateProfile(profileId);
- await optoutProfile(profileId);
-
- return (
-
- Profile activated and removal has started
-
- );
-}
diff --git a/src/app/api/utils/auth.ts b/src/app/api/utils/auth.ts
index 072086d98..1df283843 100644
--- a/src/app/api/utils/auth.ts
+++ b/src/app/api/utils/auth.ts
@@ -63,15 +63,8 @@ export const authOptions: AuthOptions = {
},
token: AppConstants.OAUTH_TOKEN_URI,
userinfo: {
- request: async (context) => {
- const response = await fetch(AppConstants.OAUTH_PROFILE_URI, {
- headers: {
- Authorization: `Bearer ${context.tokens.access_token ?? ""}`,
- },
- });
- const userInfo = (await response.json()) as Profile;
- return userInfo;
- },
+ request: async (context) =>
+ fetchUserInfo(context.tokens.access_token ?? ""),
},
clientId: AppConstants.OAUTH_CLIENT_ID,
clientSecret: AppConstants.OAUTH_CLIENT_SECRET,
@@ -86,14 +79,16 @@ export const authOptions: AuthOptions = {
twoFactorAuthentication: profile.twoFactorAuthentication,
metricsEnabled: profile.metricsEnabled,
locale: profile.locale,
- };
+ } as Profile;
},
},
],
callbacks: {
// Unused arguments also listed to show what's available:
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async jwt({ token, account, profile, trigger }) {
+ if (trigger === "update") {
+ profile = await fetchUserInfo(token.subscriber?.fxa_access_token ?? "");
+ }
if (profile) {
token.fxa = {
locale: profile.locale,
@@ -200,6 +195,16 @@ export const authOptions: AuthOptions = {
},
};
+async function fetchUserInfo(accessToken: string) {
+ const response = await fetch(AppConstants.OAUTH_PROFILE_URI, {
+ headers: {
+ Authorization: `Bearer ${accessToken ?? ""}`,
+ },
+ });
+ const userInfo = (await response.json()) as Profile;
+ return userInfo;
+}
+
export function bearerToken(req: NextRequest) {
const requestHeaders = new Headers(req.headers);
requestHeaders.get("authorization");
diff --git a/src/app/functions/server/isUserSubscribed.ts b/src/app/functions/server/isUserSubscribed.ts
deleted file mode 100644
index d57bf3d45..000000000
--- a/src/app/functions/server/isUserSubscribed.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { getServerSession } from "next-auth";
-import { authOptions } from "../../api/utils/auth";
-
-type FxaSubscriptionResponse = {
- subscriptions: Array<{
- product_id: string;
- plan_id: string;
- status: "active";
- }>;
-};
-
-export const isUserSubscribed = async () => {
- // Fetch list of subscriptions.
- let subscriptions: FxaSubscriptionResponse;
-
- const session = await getServerSession(authOptions);
- if (!session?.user?.subscriber?.id) {
- throw new Error("No session");
- }
-
- const bearerToken = session?.user.subscriber?.fxa_access_token;
- if (bearerToken) {
- const url = `${
- process.env.OAUTH_API_URI ?? ""
- }/oauth/mozilla-subscriptions/customer/billing-and-subscriptions`;
- const result = await fetch(url, {
- headers: {
- "Content-Type": "application/json",
- authorization: `Bearer ${bearerToken}`,
- },
- });
- if (result.ok) {
- subscriptions = await result.json();
- for (const subscription of subscriptions.subscriptions) {
- if (
- subscription.product_id ===
- process.env.NEXT_PUBLIC_PREMIUM_PRODUCT_ID &&
- (subscription.plan_id ===
- process.env.NEXT_PUBLIC_PREMIUM_PLAN_ID_MONTHLY_US ||
- subscription.plan_id ===
- process.env.NEXT_PUBLIC_PREMIUM_PLAN_ID_YEARLY_US) &&
- subscription.status === "active"
- ) {
- return true;
- }
- }
- return false;
- }
- } else {
- console.error("User has no bearer token");
- }
-};