зеркало из https://github.com/mozilla/fxa.git
feat(next): attempt no prompt auth on landing
Because: * Attempt to perform a no prompt authentication request to FxA when customer is first navigated to Payment Next This commit: * Adds a landing route to initiate the no prompt signin * If user is authenticated, add FxA uid to cart * Add signin error page to handle FxA error for unauthenticated users * Add placeholder generic error page. Closes #FXA-7523
This commit is contained in:
Родитель
c06042e709
Коммит
888ccaad90
|
@ -0,0 +1,53 @@
|
|||
/* 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 { signIn } from 'apps/payments/next/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // defaults to auto
|
||||
|
||||
/**
|
||||
* This landing route will initiate the OAuth no prompt signin
|
||||
* attempt with FxA.
|
||||
*
|
||||
* Only when an unexpected error occurs, will the user be redirected
|
||||
* to a generic error page.
|
||||
*
|
||||
* On successful authentication, the customer will be redirected
|
||||
* to the /new page in a "Signed In" state. (i.e. FxA uid added to cart)
|
||||
*
|
||||
* On failure the customer will be redirected to /error/auth/signin
|
||||
* where the error will be handled correctly, and ideally redirect
|
||||
* the customer to the /new page in a "Signed Out" state. (i.e. no
|
||||
* FxA uid added to the cart)
|
||||
*
|
||||
* This needs to be a route handler, since the `signIn` server
|
||||
* action needs to be executed from a route handler.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
// Replace 'landing' in the URL with 'new'
|
||||
const redirectToUrl = request.nextUrl.clone();
|
||||
redirectToUrl.pathname = redirectToUrl.pathname.replace('/landing', '/new');
|
||||
|
||||
let redirectUrl;
|
||||
try {
|
||||
redirectUrl = await signIn(
|
||||
'fxa',
|
||||
{
|
||||
redirect: false,
|
||||
redirectTo: redirectToUrl.href,
|
||||
},
|
||||
{ prompt: 'none' }
|
||||
);
|
||||
} catch (error) {
|
||||
// Log the error and redirect to /new without attempting signIn
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!redirectUrl) {
|
||||
redirectUrl = redirectToUrl.href;
|
||||
}
|
||||
|
||||
redirect(redirectUrl);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { setupCartAction } from '@fxa/payments/ui/server';
|
||||
import { auth } from 'apps/payments/next/auth';
|
||||
import { headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
|
@ -14,19 +15,30 @@ interface CheckoutNewParams {
|
|||
|
||||
export default async function CheckoutNew({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: CheckoutNewParams;
|
||||
searchParams: Record<string, string>;
|
||||
}) {
|
||||
const { offeringId, interval } = params;
|
||||
const session = await auth();
|
||||
|
||||
const fxaUid = session?.user?.id;
|
||||
const ip = (headers().get('x-forwarded-for') ?? '127.0.0.1').split(',')[0];
|
||||
|
||||
const { id: cartId } = await setupCartAction(
|
||||
interval,
|
||||
offeringId,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
fxaUid,
|
||||
ip
|
||||
);
|
||||
|
||||
redirect(`${cartId}/start`);
|
||||
const searchParamsString = new URLSearchParams(searchParams).toString();
|
||||
if (searchParamsString) {
|
||||
redirect(`${cartId}/start?${searchParamsString}`);
|
||||
} else {
|
||||
redirect(`${cartId}/start`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/* 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 { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
interface ErrorAuthSigninSearchParams {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Errors that might occur during Auth SignIn
|
||||
*
|
||||
* Most common error would be OAuthCallbackError. This error is expected if
|
||||
* no prompt signin is attempted on a user thats not currently signed into FxA.
|
||||
*
|
||||
* In situations like this, ideally the customer should be redirected to the
|
||||
* appropriate checkout page in a "signed out" state. To achieve this the
|
||||
* authjs.callback-url cookie is read to retrieve the correct redirect url,
|
||||
* otherwise it's not possible to know what offering and interval the customer
|
||||
* was attempting to reach.
|
||||
*
|
||||
*/
|
||||
export default async function Error({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: ErrorAuthSigninSearchParams;
|
||||
}) {
|
||||
const fallbackErrorPagePath = '/error';
|
||||
|
||||
if (searchParams?.error !== 'OAuthCallbackError') {
|
||||
redirect(fallbackErrorPagePath);
|
||||
}
|
||||
const cookieStore = cookies();
|
||||
const redirectUrl = cookieStore.get('authjs.callback-url');
|
||||
|
||||
if (redirectUrl?.value) {
|
||||
redirect(`${redirectUrl.value}`);
|
||||
} else {
|
||||
redirect(fallbackErrorPagePath);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/* 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/. */
|
||||
export default function ErrorGeneric() {
|
||||
return <div>Generic Error Page</div>;
|
||||
}
|
|
@ -12,25 +12,49 @@ export default function Index() {
|
|||
// This page will be fixed before launch by FXA-8304
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-center m-4">Welcome</h1>
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Monthly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/monthly/new"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
<h1 className="text-xxl text-center m-4">Welcome</h1>
|
||||
<div className="flex-col">
|
||||
<h2 className="text-xl">With auth</h2>
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Monthly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/monthly/landing"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Yearly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/yearly/landing"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Yearly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/yearly/new"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
<h2 className="text-xl mt-8">Without auth</h2>
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Monthly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/monthly/new"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 items-center">
|
||||
<h2>123Done - Yearly</h2>
|
||||
<Link
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
href="/en/123done/checkout/yearly/new"
|
||||
>
|
||||
Redirect
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,4 +6,7 @@ import type { NextAuthConfig } from 'next-auth';
|
|||
|
||||
export const authConfig = {
|
||||
providers: [], // Add providers with an empty array for now
|
||||
pages: {
|
||||
signIn: '/en-US/error/auth/signin',
|
||||
},
|
||||
} satisfies NextAuthConfig;
|
||||
|
|
|
@ -30,8 +30,15 @@ export const {
|
|||
],
|
||||
callbacks: {
|
||||
async session(params: any) {
|
||||
params.session.user.id = params.token.sub;
|
||||
params.session.user.id = params.token.fxaUid;
|
||||
return params.session;
|
||||
},
|
||||
async jwt({ token, account }) {
|
||||
const fxaUid = token.fxaUid || account?.providerAccountId;
|
||||
return {
|
||||
...token,
|
||||
fxaUid,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -10,7 +10,10 @@ import { CartManager } from './cart.manager';
|
|||
import { ResultCart, TaxAddress, UpdateCart } from './cart.types';
|
||||
import { handleEligibilityStatusMap } from './cart.utils';
|
||||
import { CartErrorReasonId, CartState } from '@fxa/shared/db/mysql/account';
|
||||
import { AccountCustomerManager } from '@fxa/payments/stripe';
|
||||
import {
|
||||
AccountCustomerManager,
|
||||
AccountCustomerNotFoundError,
|
||||
} from '@fxa/payments/stripe';
|
||||
import { GeoDBManager } from '@fxa/shared/geodb';
|
||||
import { CheckoutService } from './checkout.service';
|
||||
|
||||
|
@ -42,9 +45,17 @@ export class CartService {
|
|||
// - Check if user is eligible to subscribe to plan, else throw error
|
||||
// - Fetch invoice preview total from Stripe for `amount`
|
||||
// - Fetch stripeCustomerId if uid is passed and has a customer id
|
||||
const accountCustomer = args.uid
|
||||
? await this.accountCustomerManager.getAccountCustomerByUid(args.uid)
|
||||
: undefined;
|
||||
let accountCustomer;
|
||||
if (args.uid) {
|
||||
try {
|
||||
accountCustomer =
|
||||
await this.accountCustomerManager.getAccountCustomerByUid(args.uid);
|
||||
} catch (error) {
|
||||
if (!(error instanceof AccountCustomerNotFoundError)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const taxAddress = args.ip
|
||||
? this.geodbManager.getTaxAddress(args.ip)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
export * from './lib/accountCustomer/accountCustomer.error';
|
||||
export * from './lib/accountCustomer/accountCustomer.factories';
|
||||
export * from './lib/accountCustomer/accountCustomer.manager';
|
||||
export {
|
||||
|
|
Загрузка…
Ссылка в новой задаче