Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -15,9 +15,7 @@ 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';
Expand Down Expand Up @@ -52,16 +50,21 @@ export default async function CheckoutError({
}) {
const { locale } = params;
const acceptLanguage = headers().get('accept-language');

const cartPromise = getCartOrRedirectAction(
const session = await auth();
const cart = await getCartOrRedirectAction(
params.cartId,
SupportedPages.ERROR,
searchParams
searchParams,
session?.user?.id
);
const l10n = getApp().getL10n(acceptLanguage, locale);
const [cart] = await Promise.all([cartPromise]);

const errorReason = getErrorFtlInfo(cart.errorReasonId, params, config, searchParams);
const errorReason = getErrorFtlInfo(
cart.errorReasonId,
params,
config,
searchParams
);

return (
<>
Expand All @@ -72,7 +75,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=""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
buildPageMetadata,
} from '@fxa/payments/ui/server';
import { headers } from 'next/headers';
import { auth } from 'apps/payments/next/auth';
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
import type { Metadata } from 'next';
import { config } from 'apps/payments/next/config';
Expand Down Expand Up @@ -45,10 +46,12 @@ export default async function NeedsInputPage({
const { locale } = params;
const acceptLanguage = headers().get('accept-language');
const l10n = getApp().getL10n(acceptLanguage, locale);
const session = await auth();
const cart = await getCartOrRedirectAction(
params.cartId,
SupportedPages.NEEDS_INPUT,
searchParams
searchParams,
session?.user?.id
);
if (!cart.currency) {
throw new Error('Currency is missing from the cart');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default async function CheckoutSuccess({
}) {
const { locale } = params;
const acceptLanguage = headers().get('accept-language');
const session = await auth();

const cmsDataPromise = fetchCMSData(
params.offeringId,
Expand All @@ -56,15 +57,11 @@ export default async function CheckoutSuccess({
const cartDataPromise = getCartOrRedirectAction(
params.cartId,
SupportedPages.SUCCESS,
searchParams
searchParams,
session?.user?.id
);
const sessionPromise = auth();
const l10n = getApp().getL10n(locale);
const [cms, cart, session] = await Promise.all([
cmsDataPromise,
cartDataPromise,
sessionPromise,
]);
const [cms, cart] = await Promise.all([cmsDataPromise, cartDataPromise]);

const { successActionButtonUrl, successActionButtonLabel } =
cms.commonContent.localizations.at(0) || cms.commonContent;
Expand Down Expand Up @@ -144,7 +141,10 @@ export default async function CheckoutSuccess({
<div className="flex items-center gap-3">
<Image
src={getCardIcon('apple_pay', l10n).img}
alt={l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')}
alt={l10n.getString(
'apple-pay-logo-alt-text',
'Apple Pay logo'
)}
width={40}
height={24}
/>
Expand All @@ -153,7 +153,10 @@ export default async function CheckoutSuccess({
<div className="flex items-center gap-3">
<Image
src={getCardIcon('google_pay', l10n).img}
alt={l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')}
alt={l10n.getString(
'google-pay-logo-alt-text',
'Google Pay logo'
)}
width={40}
height={24}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -50,14 +50,14 @@ export default async function UpgradeError({
}) {
const { locale } = params;
const acceptLanguage = headers().get('accept-language');

const cartPromise = getCartOrRedirectAction(
const session = await auth();
const cart = await getCartOrRedirectAction(
params.cartId,
SupportedPages.ERROR,
searchParams
searchParams,
session?.user?.id
);
const l10n = getApp().getL10n(acceptLanguage, locale);
const [cart] = await Promise.all([cartPromise]);

const errorReason = getErrorFtlInfo(cart.errorReasonId, params, config, searchParams);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SupportedPages,
buildPageMetadata,
} from '@fxa/payments/ui/server';
import { auth } from 'apps/payments/next/auth';
import { headers } from 'next/headers';
import { getCartOrRedirectAction } from '@fxa/payments/ui/actions';
import { Metadata } from 'next';
Expand Down Expand Up @@ -45,10 +46,12 @@ export default async function NeedsInputPage({
const { locale } = params;
const acceptLanguage = headers().get('accept-language');
const l10n = getApp().getL10n(acceptLanguage, locale);
const session = await auth();
const cart = await getCartOrRedirectAction(
params.cartId,
SupportedPages.NEEDS_INPUT,
searchParams
searchParams,
session?.user?.id
);
if (!cart.currency) {
throw new Error('Currency is missing from the cart');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default async function UpgradeSuccess({
}) {
const { locale } = params;
const acceptLanguage = headers().get('accept-language');
const session = await auth();

const cmsDataPromise = fetchCMSData(
params.offeringId,
Expand All @@ -56,15 +57,11 @@ export default async function UpgradeSuccess({
const cartDataPromise = getCartOrRedirectAction(
params.cartId,
SupportedPages.SUCCESS,
searchParams
searchParams,
session?.user?.id
);
const sessionPromise = auth();
const l10n = getApp().getL10n(locale);
const [cms, cart, session] = await Promise.all([
cmsDataPromise,
cartDataPromise,
sessionPromise,
]);
const [cms, cart] = await Promise.all([cmsDataPromise, cartDataPromise]);

const { successActionButtonUrl, successActionButtonLabel } =
cms.commonContent.localizations.at(0) || cms.commonContent;
Expand Down Expand Up @@ -145,7 +142,10 @@ export default async function UpgradeSuccess({
<div className="flex items-center gap-3">
<Image
src={getCardIcon('apple_pay', l10n).img}
alt={l10n.getString('apple-pay-logo-alt-text', 'Apple Pay logo')}
alt={l10n.getString(
'apple-pay-logo-alt-text',
'Apple Pay logo'
)}
width={40}
height={24}
/>
Expand All @@ -154,7 +154,10 @@ export default async function UpgradeSuccess({
<div className="flex items-center gap-3">
<Image
src={getCardIcon('google_pay', l10n).img}
alt={l10n.getString('google-pay-logo-alt-text', 'Google Pay logo')}
alt={l10n.getString(
'google-pay-logo-alt-text',
'Google Pay logo'
)}
width={40}
height={24}
/>
Expand Down
26 changes: 19 additions & 7 deletions libs/payments/ui/src/lib/actions/getCartOrRedirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

'use server';

import { redirect } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import { getApp } from '../nestapp/app';
import { getRedirect, validateCartState } from '../utils/get-cart';
import { SupportedPages } from '../utils/types';
Expand All @@ -22,43 +22,55 @@ import {
* Get Cart or Redirect if cart state does not match supported page
* @@param cartId - Cart ID
* @@param page - Page that action is being called from
* @@param searchParams - URL search params
* @@param uid - Session user ID
*/
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages.START,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
): Promise<StartCartDTO>;
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages.PROCESSING,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
): Promise<ProcessingCartDTO>;
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages.NEEDS_INPUT,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
): Promise<NeedsInputCartDTO>;
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages.ERROR,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
): Promise<FailCartDTO>;
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages.SUCCESS,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
): Promise<SuccessCartDTO>;
async function getCartOrRedirectAction(
cartId: string,
page: SupportedPages,
searchParams?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>,
uid?: string
Comment on lines 58 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK someone can call this action with any UID they want as the last param, nullifying the security of this endpoint.

): Promise<CartDTO> {
const urlSearchParams = new URLSearchParams(searchParams);
const params = searchParams ? `?${urlSearchParams.toString()}` : '';
const cart = await getApp().getActionsService().getCart({
cartId,
});

if (cart.uid && cart.uid !== uid) {
notFound();
}

if (!validateCartState(cart.state, page)) {
redirect(getRedirect(cart.state) + params);
}
Expand Down