From 233070869ddf6d3b2b7dbc56f1eb61a7db567616 Mon Sep 17 00:00:00 2001 From: Adam Clason Date: Sat, 21 Jan 2023 17:59:53 -0600 Subject: [PATCH] Ordercloud signup hook/endpoint and stubbing out other ordercloud auth endpoints --- packages/ordercloud/package.json | 10 ++-- .../ordercloud/src/api/endpoints/index.ts | 2 + .../src/api/endpoints/signup/index.ts | 18 ++++++ .../src/api/endpoints/signup/signup.ts | 58 +++++++++++++++++++ .../ordercloud/src/api/utils/fetch-rest.ts | 30 +++++----- packages/ordercloud/src/auth/use-login.tsx | 45 +++++++++++--- packages/ordercloud/src/auth/use-logout.tsx | 30 +++++++--- packages/ordercloud/src/auth/use-signup.tsx | 43 +++++++++++--- packages/ordercloud/src/commerce.config.json | 2 +- pnpm-lock.yaml | 10 ++++ 10 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 packages/ordercloud/src/api/endpoints/signup/index.ts create mode 100644 packages/ordercloud/src/api/endpoints/signup/signup.ts diff --git a/packages/ordercloud/package.json b/packages/ordercloud/package.json index 80c6375da..fb5b4f0f1 100644 --- a/packages/ordercloud/package.json +++ b/packages/ordercloud/package.json @@ -48,8 +48,9 @@ }, "dependencies": { "@vercel/commerce": "workspace:*", - "lodash.debounce": "^4.0.8", - "cookie": "^0.4.1" + "cookie": "^0.4.1", + "jsonwebtoken": "8.5.1", + "lodash.debounce": "^4.0.8" }, "peerDependencies": { "next": "^13", @@ -60,11 +61,12 @@ "@taskr/clear": "^1.1.0", "@taskr/esnext": "^1.1.0", "@taskr/watch": "^1.1.0", + "@types/cookie": "^0.4.1", + "@types/jsonwebtoken": "8.5.7", "@types/lodash.debounce": "^4.0.6", "@types/node": "^17.0.8", - "@types/react": "^18.0.14", - "@types/cookie": "^0.4.1", "@types/node-fetch": "^2.6.2", + "@types/react": "^18.0.14", "lint-staged": "^12.1.7", "next": "^13.0.6", "prettier": "^2.5.1", diff --git a/packages/ordercloud/src/api/endpoints/index.ts b/packages/ordercloud/src/api/endpoints/index.ts index 5ea4659d0..9b200a41b 100644 --- a/packages/ordercloud/src/api/endpoints/index.ts +++ b/packages/ordercloud/src/api/endpoints/index.ts @@ -7,6 +7,7 @@ import checkout from './checkout' import products from './catalog/products' import customerCard from './customer/card' import customerAddress from './customer/address' +import signup from './signup'; const endpoints = { cart, @@ -14,6 +15,7 @@ const endpoints = { 'customer/card': customerCard, 'customer/address': customerAddress, 'catalog/products': products, + 'signup': signup } export default function ordercloudAPI(commerce: OrdercloudAPI) { diff --git a/packages/ordercloud/src/api/endpoints/signup/index.ts b/packages/ordercloud/src/api/endpoints/signup/index.ts new file mode 100644 index 000000000..d667530b3 --- /dev/null +++ b/packages/ordercloud/src/api/endpoints/signup/index.ts @@ -0,0 +1,18 @@ +import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' +import signupEndpoint from '@vercel/commerce/api/endpoints/signup' +import type { SignupSchema } from '@vercel/commerce/types/signup' +import type { OrdercloudAPI } from '../..' +import signup from './signup' + +export type SignupAPI = GetAPISchema + +export type SignupEndpoint = SignupAPI['endpoint'] + +export const handlers: SignupEndpoint['handlers'] = { signup } + +const singupApi = createEndpoint({ + handler: signupEndpoint, + handlers, +}) + +export default singupApi diff --git a/packages/ordercloud/src/api/endpoints/signup/signup.ts b/packages/ordercloud/src/api/endpoints/signup/signup.ts new file mode 100644 index 000000000..bb8b1a3ef --- /dev/null +++ b/packages/ordercloud/src/api/endpoints/signup/signup.ts @@ -0,0 +1,58 @@ +import type { SignupEndpoint } from '.' + +import { decode, type JwtPayload } from 'jsonwebtoken' +import { serialize } from 'cookie' +import { access } from 'fs' + +const signup: SignupEndpoint['handlers']['signup'] = async ({ + req, + body: { firstName, lastName, password, email }, + config: { restBuyerFetch, tokenCookie }, +}) => { + // Get token + const token = req.cookies.get(tokenCookie)?.value + let headers: any = {} + + const accessToken = await restBuyerFetch( + 'PUT', + `/me/register`, + { + Username: email, + Password: password, + FirstName: firstName, + LastName: lastName, + Email: email, + Active: true, + }, + { + token, + anonToken: true, + } + ).then((response: any) => { + return response.access_token + }) + + console.log('got access token: ', accessToken) + + if (accessToken) { + const decodedToken = decode(accessToken) as JwtPayload + + console.log('decoded: ', decodedToken) + + return { + headers: { + 'Set-Cookie': serialize(tokenCookie, accessToken, { + maxAge: decodedToken.exp, + expires: new Date(Date.now() + decodedToken.exp! * 1000), + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + }), + }, + } + } + + return { data: undefined, headers } +} + +export default signup diff --git a/packages/ordercloud/src/api/utils/fetch-rest.ts b/packages/ordercloud/src/api/utils/fetch-rest.ts index 3724ebe84..b767bfab5 100644 --- a/packages/ordercloud/src/api/utils/fetch-rest.ts +++ b/packages/ordercloud/src/api/utils/fetch-rest.ts @@ -57,21 +57,23 @@ export async function fetchData(opts: { // Destructure opts const { path, body, fetchOptions, config, token, method = 'GET' } = opts + let url = `${config.commerceUrl}/${config.apiVersion}${path}` + if (fetchOptions?.anonToken) { + url += `?anonUserToken=${encodeURIComponent(token)}` + } + // Do the request with the correct headers - const dataResponse = await fetch( - `${config.commerceUrl}/${config.apiVersion}${path}`, - { - ...fetchOptions, - method, - headers: { - ...fetchOptions?.headers, - 'Content-Type': 'application/json', - accept: 'application/json, text/plain, */*', - authorization: `Bearer ${token}`, - }, - body: body ? JSON.stringify(body) : undefined, - } - ) + const dataResponse = await fetch(url, { + ...fetchOptions, + method, + headers: { + ...fetchOptions?.headers, + 'Content-Type': 'application/json', + accept: 'application/json, text/plain, */*', + authorization: `Bearer ${token}`, + }, + body: body ? JSON.stringify(body) : undefined, + }) // If something failed getting the data response if (!dataResponse.ok) { diff --git a/packages/ordercloud/src/auth/use-login.tsx b/packages/ordercloud/src/auth/use-login.tsx index 20e3ed229..e85404b5d 100644 --- a/packages/ordercloud/src/auth/use-login.tsx +++ b/packages/ordercloud/src/auth/use-login.tsx @@ -1,16 +1,43 @@ -import { MutationHook } from '@vercel/commerce/utils/types' -import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login' +import type { MutationHook } from '@vercel/commerce/utils/types' +import useLogin, { type UseLogin } from '@vercel/commerce/auth/use-login' +import { useCallback } from 'react' +import { CommerceError } from '@vercel/commerce/utils/errors' +import type { LoginHook } from '@vercel/commerce/types/login' +import useCustomer from '../customer/use-customer' +import useCart from '../cart/use-cart' export default useLogin as UseLogin -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/commerce/login', + method: 'POST', }, - async fetcher() { - return null - }, - useHook: () => () => { - return async function () {} + async fetcher({ input: { email, password }, options, fetch }) { + if (!(email && password)) { + throw new CommerceError({ + message: 'An email and password are required to login', + }) + } + + return fetch({ + ...options, + body: { email, password }, + }) }, + useHook: + ({ fetch }) => + () => { + const { mutate } = useCustomer() + const { mutate: mutateCart } = useCart() + return useCallback( + async function login(input) { + const data = await fetch({ input }) + await mutate() + await mutateCart() + return data + }, + [fetch, mutate, mutateCart] + ) + }, } diff --git a/packages/ordercloud/src/auth/use-logout.tsx b/packages/ordercloud/src/auth/use-logout.tsx index 4e74908f3..377180ccc 100644 --- a/packages/ordercloud/src/auth/use-logout.tsx +++ b/packages/ordercloud/src/auth/use-logout.tsx @@ -1,17 +1,31 @@ -import { MutationHook } from '@vercel/commerce/utils/types' +import { useCallback } from 'react' +import type { MutationHook } from '@vercel/commerce/utils/types' import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout' +import type { LogoutHook } from '@vercel/commerce/types/logout' +import useCustomer from '../customer/use-customer' +import useCart from '../cart/use-cart' export default useLogout as UseLogout -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', - }, - async fetcher() { - return null + url: '/api/commerce/logout', + method: 'GET', }, useHook: ({ fetch }) => - () => - async () => {}, + () => { + const { mutate } = useCustomer() + const { mutate: mutateCart } = useCart() + + return useCallback( + async function logout() { + const data = await fetch() + await mutate(null, false) + await mutateCart(null, false) + return data + }, + [fetch, mutate, mutateCart] + ) + }, } diff --git a/packages/ordercloud/src/auth/use-signup.tsx b/packages/ordercloud/src/auth/use-signup.tsx index e48811403..3d6957927 100644 --- a/packages/ordercloud/src/auth/use-signup.tsx +++ b/packages/ordercloud/src/auth/use-signup.tsx @@ -1,19 +1,46 @@ import { useCallback } from 'react' +import type { MutationHook } from '@vercel/commerce/utils/types' +import { CommerceError } from '@vercel/commerce/utils/errors' +import useSignup, { type UseSignup } from '@vercel/commerce/auth/use-signup' +import type { SignupHook } from '@vercel/commerce/types/signup' import useCustomer from '../customer/use-customer' -import { MutationHook } from '@vercel/commerce/utils/types' -import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup' export default useSignup as UseSignup -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/commerce/signup', + method: 'POST', }, - async fetcher() { - return null + async fetcher({ + input: { firstName, lastName, email, password }, + options, + fetch, + }) { + if (!(firstName && lastName && email && password)) { + throw new CommerceError({ + message: + 'A first name, last name, email and password are required to signup', + }) + } + + return fetch({ + ...options, + body: { firstName, lastName, email, password }, + }) }, useHook: ({ fetch }) => - () => - () => {}, + () => { + //const { mutate } = useCustomer() TODO add mutate back in once useCustomer is implemented + + return useCallback( + async function signup(input) { + const data = await fetch({ input }) + // await mutate() + return data + }, + [fetch] //mutate] + ) + }, } diff --git a/packages/ordercloud/src/commerce.config.json b/packages/ordercloud/src/commerce.config.json index e329bd4c1..09e25c02d 100644 --- a/packages/ordercloud/src/commerce.config.json +++ b/packages/ordercloud/src/commerce.config.json @@ -4,7 +4,7 @@ "wishlist": false, "cart": true, "search": true, - "customerAuth": false, + "customerAuth": true, "customCheckout": true } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3669499f1..54dd78ea0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,12 +263,14 @@ importers: '@taskr/esnext': ^1.1.0 '@taskr/watch': ^1.1.0 '@types/cookie': ^0.4.1 + '@types/jsonwebtoken': 8.5.7 '@types/lodash.debounce': ^4.0.6 '@types/node': ^17.0.8 '@types/node-fetch': ^2.6.2 '@types/react': ^18.0.14 '@vercel/commerce': workspace:* cookie: ^0.4.1 + jsonwebtoken: 8.5.1 lint-staged: ^12.1.7 lodash.debounce: ^4.0.8 next: ^13.0.6 @@ -281,12 +283,14 @@ importers: dependencies: '@vercel/commerce': link:../commerce cookie: 0.4.2 + jsonwebtoken: 8.5.1 lodash.debounce: 4.0.8 devDependencies: '@taskr/clear': 1.1.0 '@taskr/esnext': 1.1.0 '@taskr/watch': 1.1.0 '@types/cookie': 0.4.1 + '@types/jsonwebtoken': 8.5.7 '@types/lodash.debounce': 4.0.7 '@types/node': 17.0.45 '@types/node-fetch': 2.6.2 @@ -3398,6 +3402,12 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/jsonwebtoken/8.5.7: + resolution: {integrity: sha512-CBHN+1unePowgS94ayLE7aVp7AfyhgG/3l2O+AjkhOMY4kAAfVI1OnbbLnOeDMAdTNLP5ZjJ3kdZanRtRQaK3Q==} + dependencies: + '@types/node': 18.7.18 + dev: true + /@types/jsonwebtoken/8.5.9: resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==} dependencies: