mirror of
https://github.com/mozilla/fxa.git
synced 2025-12-27 22:54:41 +00:00
feat(payments-next): Add redirects on mismatched cart uids
Because: * Users can currently view carts associated with another account's uid when provided with the URL This commit: * Adds in a frontend intercept that redirects the user to the start of the checkout process if the request cart's UID does not match the signed in user's UID Closes #PAY-3155
This commit is contained in:
parent
866e34d928
commit
f88ca40d9c
@ -5,7 +5,7 @@
|
||||
import { headers } from 'next/headers';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import errorIcon from '@fxa/shared/assets/images/error.svg';
|
||||
import checkIcon from '@fxa/shared/assets/images/check.svg';
|
||||
import {
|
||||
@ -15,12 +15,12 @@ import {
|
||||
getErrorFtlInfo,
|
||||
buildPageMetadata,
|
||||
} from '@fxa/payments/ui/server';
|
||||
import {
|
||||
getCartOrRedirectAction,
|
||||
} from '@fxa/payments/ui/actions';
|
||||
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import type { Metadata } from 'next';
|
||||
import { CartErrorReasonId } from '@fxa/shared/db/mysql/account';
|
||||
import { buildRedirectUrl } from '@fxa/payments/ui';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
// forces dynamic rendering
|
||||
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
|
||||
@ -53,15 +53,40 @@ export default async function CheckoutError({
|
||||
const { locale } = params;
|
||||
const acceptLanguage = headers().get('accept-language');
|
||||
|
||||
const sessionPromise = auth();
|
||||
const cartPromise = getCartOrRedirectAction(
|
||||
params.cartId,
|
||||
SupportedPages.ERROR,
|
||||
searchParams
|
||||
);
|
||||
const l10n = getApp().getL10n(acceptLanguage, locale);
|
||||
const [cart] = await Promise.all([cartPromise]);
|
||||
const [cart, session] = await Promise.all([cartPromise, sessionPromise]);
|
||||
|
||||
const errorReason = getErrorFtlInfo(cart.errorReasonId, params, config, searchParams);
|
||||
const errorReason = getErrorFtlInfo(
|
||||
cart.errorReasonId,
|
||||
params,
|
||||
config,
|
||||
searchParams
|
||||
);
|
||||
|
||||
if (cart.id && cart.uid !== session?.user?.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -72,7 +97,7 @@ export default async function CheckoutError({
|
||||
{
|
||||
// Once more conditionals are added, move this to a separate component
|
||||
cart.errorReasonId ===
|
||||
CartErrorReasonId.CART_ELIGIBILITY_STATUS_SAME ? (
|
||||
CartErrorReasonId.CART_ELIGIBILITY_STATUS_SAME ? (
|
||||
<Image
|
||||
src={checkIcon}
|
||||
alt=""
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
LoadingSpinner,
|
||||
StripeWrapper,
|
||||
PaymentInputHandler,
|
||||
buildRedirectUrl,
|
||||
} from '@fxa/payments/ui';
|
||||
import {
|
||||
getApp,
|
||||
@ -17,6 +18,8 @@ import { headers } from 'next/headers';
|
||||
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
|
||||
import type { Metadata } from 'next';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@ -45,14 +48,38 @@ export default async function NeedsInputPage({
|
||||
const { locale } = params;
|
||||
const acceptLanguage = headers().get('accept-language');
|
||||
const l10n = getApp().getL10n(acceptLanguage, locale);
|
||||
const cart = await getCartOrRedirectAction(
|
||||
|
||||
const sessionPromise = auth();
|
||||
const cartPromise = getCartOrRedirectAction(
|
||||
params.cartId,
|
||||
SupportedPages.NEEDS_INPUT,
|
||||
searchParams
|
||||
);
|
||||
const [session, cart] = await Promise.all([sessionPromise, cartPromise]);
|
||||
|
||||
if (!cart.currency) {
|
||||
throw new Error('Currency is missing from the cart');
|
||||
}
|
||||
|
||||
if (!session?.user?.id ||cart.uid !== session.user.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="flex flex-col text-center text-sm"
|
||||
|
||||
@ -8,7 +8,7 @@ import Image from 'next/image';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
|
||||
import { SubPlatPaymentMethodType } from '@fxa/payments/customer';
|
||||
import { getCardIcon } from '@fxa/payments/ui';
|
||||
import { buildRedirectUrl, getCardIcon } from '@fxa/payments/ui';
|
||||
import {
|
||||
fetchCMSData,
|
||||
getCartOrRedirectAction,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
buildPageMetadata,
|
||||
} from '@fxa/payments/ui/server';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@ -68,6 +69,25 @@ export default async function CheckoutSuccess({
|
||||
sessionPromise,
|
||||
]);
|
||||
|
||||
if (!session?.user?.id || cart.uid !== session.user.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
const { successActionButtonUrl, successActionButtonLabel } =
|
||||
cms.commonContent.localizations.at(0) || cms.commonContent;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { headers } from 'next/headers';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import errorIcon from '@fxa/shared/assets/images/error.svg';
|
||||
import {
|
||||
getApp,
|
||||
@ -14,11 +14,11 @@ import {
|
||||
getErrorFtlInfo,
|
||||
buildPageMetadata,
|
||||
} from '@fxa/payments/ui/server';
|
||||
import {
|
||||
getCartOrRedirectAction,
|
||||
} from '@fxa/payments/ui/actions';
|
||||
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import { Metadata } from 'next';
|
||||
import { buildRedirectUrl } from '@fxa/payments/ui';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
// forces dynamic rendering
|
||||
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
|
||||
@ -51,15 +51,40 @@ export default async function UpgradeError({
|
||||
const { locale } = params;
|
||||
const acceptLanguage = headers().get('accept-language');
|
||||
|
||||
const sessionPromise = auth();
|
||||
const cartPromise = getCartOrRedirectAction(
|
||||
params.cartId,
|
||||
SupportedPages.ERROR,
|
||||
searchParams
|
||||
);
|
||||
const l10n = getApp().getL10n(acceptLanguage, locale);
|
||||
const [cart] = await Promise.all([cartPromise]);
|
||||
const [cart, session] = await Promise.all([cartPromise, sessionPromise]);
|
||||
|
||||
const errorReason = getErrorFtlInfo(cart.errorReasonId, params, config, searchParams);
|
||||
const errorReason = getErrorFtlInfo(
|
||||
cart.errorReasonId,
|
||||
params,
|
||||
config,
|
||||
searchParams
|
||||
);
|
||||
|
||||
if (cart.id && cart.uid !== session?.user?.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
LoadingSpinner,
|
||||
StripeWrapper,
|
||||
PaymentInputHandler,
|
||||
buildRedirectUrl,
|
||||
} from '@fxa/payments/ui';
|
||||
import {
|
||||
getApp,
|
||||
@ -17,6 +18,8 @@ import { headers } from 'next/headers';
|
||||
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
|
||||
import { Metadata } from 'next';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@ -45,14 +48,36 @@ export default async function NeedsInputPage({
|
||||
const { locale } = params;
|
||||
const acceptLanguage = headers().get('accept-language');
|
||||
const l10n = getApp().getL10n(acceptLanguage, locale);
|
||||
const cart = await getCartOrRedirectAction(
|
||||
const cartPromise = getCartOrRedirectAction(
|
||||
params.cartId,
|
||||
SupportedPages.NEEDS_INPUT,
|
||||
searchParams
|
||||
);
|
||||
const sessionPromise = auth();
|
||||
const [session, cart] = await Promise.all([sessionPromise, cartPromise]);
|
||||
if (!cart.currency) {
|
||||
throw new Error('Currency is missing from the cart');
|
||||
}
|
||||
|
||||
if (!session?.user?.id || cart.uid !== session.user.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="flex flex-col text-center text-sm"
|
||||
|
||||
@ -8,7 +8,7 @@ import { Metadata } from 'next';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
|
||||
import { SubPlatPaymentMethodType } from '@fxa/payments/customer';
|
||||
import { getCardIcon } from '@fxa/payments/ui';
|
||||
import { buildRedirectUrl, getCardIcon } from '@fxa/payments/ui';
|
||||
import {
|
||||
fetchCMSData,
|
||||
getCartOrRedirectAction,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
buildPageMetadata,
|
||||
} from '@fxa/payments/ui/server';
|
||||
import { config } from 'apps/payments/next/config';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@ -68,6 +69,25 @@ export default async function UpgradeSuccess({
|
||||
sessionPromise,
|
||||
]);
|
||||
|
||||
if (!session?.user?.id || cart.uid !== session.user.id) {
|
||||
const redirectSearchParams: Record<string, string | string[]> =
|
||||
searchParams || {};
|
||||
delete redirectSearchParams.cartId;
|
||||
delete redirectSearchParams.cartVersion;
|
||||
const redirectTo = buildRedirectUrl(
|
||||
params.offeringId,
|
||||
params.interval,
|
||||
'new',
|
||||
'checkout',
|
||||
{
|
||||
baseUrl: config.paymentsNextHostedUrl,
|
||||
locale,
|
||||
searchParams: redirectSearchParams,
|
||||
}
|
||||
);
|
||||
redirect(redirectTo);
|
||||
}
|
||||
|
||||
const { successActionButtonUrl, successActionButtonLabel } =
|
||||
cms.commonContent.localizations.at(0) || cms.commonContent;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user