MNTOR-2224 - allow updating FxA profile info from front-end without sign-out (#3474)
* MNTOR-2224 - refresh FxA profile when update() from useSession is called --------- Co-authored-by: Vincent <Vinnl@users.noreply.github.com>
This commit is contained in:
Родитель
36f9bce11b
Коммит
c29b4a7d81
|
@ -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 <link_to_breach_site>{ $breach_name }</link_to_breach_site>.
|
||||
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…
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<div>
|
||||
<h3>You are now subscribed</h3>
|
||||
{dev ? (
|
||||
<div>
|
||||
<h3>Dev mode enabled</h3>
|
||||
<p>Reload this page to update scan</p>
|
||||
<pre>{JSON.stringify(scans, null, 2)}</pre>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <div>{l10n.getString("subscription-check-loading")}</div>;
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<div>
|
||||
<h3>You are now unsubscribed</h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<Script id="redirect">window.location = "results";</Script>
|
||||
);
|
||||
}
|
||||
|
||||
const result = await getOnerepProfileId(session.user.subscriber.id);
|
||||
const profileId = result[0]["onerep_profile_id"] as number;
|
||||
await activateProfile(profileId);
|
||||
await optoutProfile(profileId);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h2>Profile activated and removal has started</h2>
|
||||
</main>
|
||||
);
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
};
|
Загрузка…
Ссылка в новой задаче