diff --git a/components/common/UserNav/UserNav.tsx b/components/common/UserNav/UserNav.tsx index 5d7d28df3..07e1af04d 100644 --- a/components/common/UserNav/UserNav.tsx +++ b/components/common/UserNav/UserNav.tsx @@ -40,7 +40,7 @@ const UserNav: FC = ({ className }) => { )} - {process.env.COMMERCE_CUSTOMERAUTH_ENABLED && ( +
  • {customer ? ( @@ -54,7 +54,7 @@ const UserNav: FC = ({ className }) => { )}
  • - )} + ) diff --git a/framework/kibocommerce/api/endpoints/login/index.ts b/framework/kibocommerce/api/endpoints/login/index.ts index 491bf0ac9..49476c7f6 100644 --- a/framework/kibocommerce/api/endpoints/login/index.ts +++ b/framework/kibocommerce/api/endpoints/login/index.ts @@ -1 +1,20 @@ -export default function noopApi(...args: any[]): void {} +// export default function noopApi(...args: any[]): void {} + +import { GetAPISchema, createEndpoint } from '@commerce/api' +import loginEndpoint from '@commerce/api/endpoints/login' +import type { LoginSchema } from '../../../types/login' +import type { KiboCommerceAPI } from '../..' +import login from './login' + +export type LoginAPI = GetAPISchema + +export type LoginEndpoint = LoginAPI['endpoint'] + +export const handlers: LoginEndpoint['handlers'] = { login } + +export const loginApi = createEndpoint({ + handler: loginEndpoint, + handlers, +}) + + diff --git a/framework/kibocommerce/api/endpoints/login/login.ts b/framework/kibocommerce/api/endpoints/login/login.ts new file mode 100644 index 000000000..eb882d998 --- /dev/null +++ b/framework/kibocommerce/api/endpoints/login/login.ts @@ -0,0 +1,62 @@ +import Cookies from 'js-cookie' +import { FetcherError } from '@commerce/utils/errors' +import type { LoginEndpoint } from '.' +import { loginMutation } from '../../mutations/login-mutation' +import {prepareSetCookie} from '../../../lib/prepareSetCookie'; +import {setCookies} from '../../../lib/setCookie' + +const invalidCredentials = /invalid credentials/i + +let response; + +const login: LoginEndpoint['handlers']['login'] = async ({ + req, + res, + body: { email, password }, + config, + commerce, +}) => { + + if (!(email && password)) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + try { + + response = await config.fetch(loginMutation, { variables: { loginInput : { username: email, password }}}) + const { account: token } = response.data; + + const authCookie = prepareSetCookie( + config.customerCookie, + JSON.stringify(token), + token.accessTokenExpiration ? { expires: new Date(token.accessTokenExpiration) }: {}, + ) + setCookies(res, [authCookie]) + + } catch (error) { + // Check if the email and password didn't match an existing account + if ( + error instanceof FetcherError && + invalidCredentials.test(error.message) + ) { + return res.status(401).json({ + data: null, + errors: [ + { + message: + 'Cannot find an account that matches the provided credentials', + code: 'invalid_credentials', + }, + ], + }) + } + + throw error + } + + res.status(200).json({ data: response }) +} + +export default login \ No newline at end of file diff --git a/framework/kibocommerce/api/index.ts b/framework/kibocommerce/api/index.ts index 3999d6b48..3f6c9dd47 100644 --- a/framework/kibocommerce/api/index.ts +++ b/framework/kibocommerce/api/index.ts @@ -1,6 +1,6 @@ import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api' import { getCommerceApi as commerceApi } from '@commerce/api' -import createFetcher from './utils/fetch-local' +import fetchGraphqlApi from './utils/fetch-local' import getAllPages from './operations/get-all-pages' import getPage from './operations/get-page' @@ -12,12 +12,12 @@ import getProduct from './operations/get-product' export interface KiboCommerceConfig extends CommerceAPIConfig {} const config: KiboCommerceConfig = { - commerceUrl: process.env.KIBO_API_URL || '', - apiToken: process.env.KIBO_API_TOKEN || '', - cartCookie: process.env.KIBO_CART_COOKIE || '', - customerCookie: process.env.KIBO_CUSTOMER_COOKIE || '', + commerceUrl: process.env.KIBO_API_URL || 'https://t17194-s21127.dev10.kubedev.kibo-dev.com/graphql', + apiToken: process.env.KIBO_API_TOKEN || `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL3d3dy5raWJvY29tbWVyY2UuY29tL2FwcF9jbGFpbXMiOnsic3NsIjoiMCIsImVudCI6IjEiLCJtdHIiOiIxIiwidWMiOiIwIiwicm5kIjoxNDQ1NjE5MDQ0LCJhaWQiOiJraWJvLWRldmVsb3Blci1rZXkiLCJha2V5Ijoia2liby1kZXZlbG9wZXIta2V5IiwiYnYiOlswLC0xNTc3NzkyMiwxLDIwNzkwNjQwNjMsMiwyMTQ3NDgzNTgyLDMsLTk3LDQsODE5MSw1LC0xNzE1Myw2LC0xMzQyMTc3MjksNywtMSw4LDQxOTQzMDMsMzEsODM4ODM1Ml0sImV4cCI6IjIwMjYtMDQtMjlUMTg6NDA6MDQiLCJlbnYiOiJkZXYxMCJ9LCJuYmYiOjE2MTk3MjE2MDUsImV4cCI6MTc3NzQ4ODAwNCwiaWF0IjoxNjE5NzIxNjA1LCJpc3MiOiJodHRwczovL3d3dy5raWJvY29tbWVyY2UuY29tIiwiYXVkIjoiaHR0cHM6Ly93d3cua2lib2NvbW1lcmNlLmNvbSJ9.xn2i0-1bmH65x_z51UQVRDY8fwn4NXnnD5JtIhcrUkw`, + cartCookie: process.env.KIBO_CART_COOKIE || 'kibo_car', + customerCookie: process.env.KIBO_CUSTOMER_COOKIE || 'kibo_customer', cartCookieMaxAge: 2592000, - fetch: createFetcher(() => getCommerceApi().getConfig()), + fetch: fetchGraphqlApi(() => getCommerceApi().getConfig()), } const operations = { diff --git a/framework/kibocommerce/api/mutations/login-mutation.ts b/framework/kibocommerce/api/mutations/login-mutation.ts new file mode 100644 index 000000000..61a9d03a5 --- /dev/null +++ b/framework/kibocommerce/api/mutations/login-mutation.ts @@ -0,0 +1,20 @@ + +export const loginMutation = ` +mutation login($loginInput:CustomerUserAuthInfoInput!) { + account:createCustomerAuthTicket(customerUserAuthInfoInput:$loginInput) { + accessToken + userId + refreshToken + refreshTokenExpiration + accessTokenExpiration + customerAccount { + id + firstName + lastName + emailAddress + userName + } + } + } +` + diff --git a/framework/kibocommerce/api/utils/fetch-local.ts b/framework/kibocommerce/api/utils/fetch-local.ts index e6ad35ba2..2612188a9 100644 --- a/framework/kibocommerce/api/utils/fetch-local.ts +++ b/framework/kibocommerce/api/utils/fetch-local.ts @@ -8,9 +8,11 @@ const fetchGraphqlApi: (getConfig: () => KiboCommerceConfig) => GraphQLFetcher = async (query: string, { variables, preview } = {}, fetchOptions) => { const config = getConfig() const res = await fetch(config.commerceUrl, { + //const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), { ...fetchOptions, method: 'POST', headers: { + Authorization: `Bearer ${config.apiToken}`, ...fetchOptions?.headers, 'Content-Type': 'application/json', }, @@ -23,7 +25,7 @@ const fetchGraphqlApi: (getConfig: () => KiboCommerceConfig) => GraphQLFetcher = const json = await res.json() if (json.errors) { throw new FetcherError({ - errors: json.errors ?? [{ message: 'Failed to fetch for API' }], + errors: json.errors ?? [{ message: 'Failed to fetch KiboCommerce API' }], status: res.status, }) } diff --git a/framework/kibocommerce/auth/use-login.tsx b/framework/kibocommerce/auth/use-login.tsx index 28351dc7f..c0197e4c2 100644 --- a/framework/kibocommerce/auth/use-login.tsx +++ b/framework/kibocommerce/auth/use-login.tsx @@ -1,16 +1,40 @@ import { MutationHook } from '@commerce/utils/types' import useLogin, { UseLogin } from '@commerce/auth/use-login' +import { useCallback } from 'react' +import { CommerceError } from '@commerce/utils/errors' +import type { LoginHook } from '../types/login' +import useCustomer from '../customer/use-customer' export default useLogin as UseLogin -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/login', + method: 'POST' }, - async fetcher() { - return null + 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: () => () => { - return async function () {} + useHook: ({ fetch }) => () => { + const { revalidate } = useCustomer() + + return useCallback( + async function login(input) { + const data = await fetch({ input }) + // await revalidate() + return data + }, + [fetch, revalidate] + ) }, } diff --git a/framework/kibocommerce/commerce.config.json b/framework/kibocommerce/commerce.config.json index 0871b8849..8992569c6 100644 --- a/framework/kibocommerce/commerce.config.json +++ b/framework/kibocommerce/commerce.config.json @@ -4,6 +4,6 @@ "wishlist": false, "cart": false, "search": false, - "customerAuth": false + "customerAuth": true } -} +} \ No newline at end of file diff --git a/framework/kibocommerce/lib/prepareSetCookie.ts b/framework/kibocommerce/lib/prepareSetCookie.ts new file mode 100644 index 000000000..3d9b3380a --- /dev/null +++ b/framework/kibocommerce/lib/prepareSetCookie.ts @@ -0,0 +1,13 @@ +export function prepareSetCookie(name: string, value: string, options: any = {}): string { + const cookieValue = [`${name}=${value}`]; + + if (options.maxAge) { + cookieValue.push(`Max-Age=${options.maxAge}`); + } + + if (options.expires && !options.maxAge) { + cookieValue.push(`Expires=${options.expires.toUTCString()}`); + } + + return cookieValue.join('; '); +} \ No newline at end of file diff --git a/framework/kibocommerce/lib/setCookie.ts b/framework/kibocommerce/lib/setCookie.ts new file mode 100644 index 000000000..2c194c921 --- /dev/null +++ b/framework/kibocommerce/lib/setCookie.ts @@ -0,0 +1,3 @@ +export function setCookies(res: any, cookies: string[]): void { + res.setHeader('Set-Cookie', cookies); +} \ No newline at end of file diff --git a/framework/kibocommerce/schema.d.ts b/framework/kibocommerce/schema.d.ts index cf52ddec9..430ce10b9 100644 --- a/framework/kibocommerce/schema.d.ts +++ b/framework/kibocommerce/schema.d.ts @@ -11397,3 +11397,8 @@ export type WorkflowStateInput = { shipmentState?: Maybe taskList?: Maybe>> } + +export type LoginMutationVariables = Exact<{ + email: Scalars['String'] + password: Scalars['String'] +}> \ No newline at end of file diff --git a/framework/kibocommerce/types/login.ts b/framework/kibocommerce/types/login.ts new file mode 100644 index 000000000..24d5077ff --- /dev/null +++ b/framework/kibocommerce/types/login.ts @@ -0,0 +1,8 @@ +import * as Core from '@commerce/types/login' +import type { LoginMutationVariables } from '../schema' + +export * from '@commerce/types/login' + +export type LoginOperation = Core.LoginOperation & { + variables: LoginMutationVariables +} diff --git a/pages/api/login.ts b/pages/api/login.ts index 9d0b6ae57..474209523 100644 --- a/pages/api/login.ts +++ b/pages/api/login.ts @@ -1,4 +1,4 @@ -import loginApi from '@framework/api/endpoints/login' +import { loginApi } from '@framework/api/endpoints/login' import commerce from '@lib/api/commerce' export default loginApi(commerce)