@faststore/api 3.89.4 → 3.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/__generated__/schema.d.ts +2 -0
  3. package/dist/cjs/src/__generated__/schema.d.ts.map +1 -1
  4. package/dist/cjs/src/__generated__/schema.js.map +1 -1
  5. package/dist/cjs/src/directives/auth.d.ts +4 -0
  6. package/dist/cjs/src/directives/auth.d.ts.map +1 -0
  7. package/dist/cjs/src/directives/auth.js +27 -0
  8. package/dist/cjs/src/directives/auth.js.map +1 -0
  9. package/dist/cjs/src/index.d.ts +3 -2
  10. package/dist/cjs/src/index.d.ts.map +1 -1
  11. package/dist/cjs/src/index.js +6 -2
  12. package/dist/cjs/src/index.js.map +1 -1
  13. package/dist/cjs/src/platforms/vtex/clients/commerce/index.js +1 -1
  14. package/dist/cjs/src/platforms/vtex/clients/commerce/index.js.map +1 -1
  15. package/dist/cjs/src/platforms/vtex/clients/commerce/types/Session.d.ts +3 -0
  16. package/dist/cjs/src/platforms/vtex/clients/commerce/types/Session.d.ts.map +1 -1
  17. package/dist/cjs/src/platforms/vtex/index.d.ts +1 -1
  18. package/dist/cjs/src/platforms/vtex/resolvers/query.d.ts +1 -1
  19. package/dist/cjs/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
  20. package/dist/cjs/src/platforms/vtex/resolvers/query.js +10 -20
  21. package/dist/cjs/src/platforms/vtex/resolvers/query.js.map +1 -1
  22. package/dist/cjs/src/platforms/vtex/resolvers/validateSession.d.ts.map +1 -1
  23. package/dist/cjs/src/platforms/vtex/resolvers/validateSession.js +24 -6
  24. package/dist/cjs/src/platforms/vtex/resolvers/validateSession.js.map +1 -1
  25. package/dist/cjs/src/platforms/vtex/utils/auth.d.ts +6 -0
  26. package/dist/cjs/src/platforms/vtex/utils/auth.d.ts.map +1 -1
  27. package/dist/cjs/src/platforms/vtex/utils/auth.js +26 -1
  28. package/dist/cjs/src/platforms/vtex/utils/auth.js.map +1 -1
  29. package/dist/cjs/src/platforms/vtex/utils/cookies.d.ts.map +1 -1
  30. package/dist/cjs/src/platforms/vtex/utils/cookies.js +36 -3
  31. package/dist/cjs/src/platforms/vtex/utils/cookies.js.map +1 -1
  32. package/dist/cjs/src/typeDefs/query.graphql +21 -16
  33. package/dist/cjs/src/typeDefs/session.graphql +2 -0
  34. package/dist/esm/package.json +1 -1
  35. package/dist/esm/src/__generated__/schema.d.ts +2 -0
  36. package/dist/esm/src/__generated__/schema.d.ts.map +1 -1
  37. package/dist/esm/src/__generated__/schema.js.map +1 -1
  38. package/dist/esm/src/directives/auth.d.ts +4 -0
  39. package/dist/esm/src/directives/auth.d.ts.map +1 -0
  40. package/dist/esm/src/directives/auth.js +25 -0
  41. package/dist/esm/src/directives/auth.js.map +1 -0
  42. package/dist/esm/src/index.d.ts +3 -2
  43. package/dist/esm/src/index.d.ts.map +1 -1
  44. package/dist/esm/src/index.js +4 -2
  45. package/dist/esm/src/index.js.map +1 -1
  46. package/dist/esm/src/platforms/vtex/clients/commerce/index.js +1 -1
  47. package/dist/esm/src/platforms/vtex/clients/commerce/index.js.map +1 -1
  48. package/dist/esm/src/platforms/vtex/clients/commerce/types/Session.d.ts +3 -0
  49. package/dist/esm/src/platforms/vtex/clients/commerce/types/Session.d.ts.map +1 -1
  50. package/dist/esm/src/platforms/vtex/index.d.ts +1 -1
  51. package/dist/esm/src/platforms/vtex/resolvers/query.d.ts +1 -1
  52. package/dist/esm/src/platforms/vtex/resolvers/query.d.ts.map +1 -1
  53. package/dist/esm/src/platforms/vtex/resolvers/query.js +10 -20
  54. package/dist/esm/src/platforms/vtex/resolvers/query.js.map +1 -1
  55. package/dist/esm/src/platforms/vtex/resolvers/validateSession.d.ts.map +1 -1
  56. package/dist/esm/src/platforms/vtex/resolvers/validateSession.js +24 -6
  57. package/dist/esm/src/platforms/vtex/resolvers/validateSession.js.map +1 -1
  58. package/dist/esm/src/platforms/vtex/utils/auth.d.ts +6 -0
  59. package/dist/esm/src/platforms/vtex/utils/auth.d.ts.map +1 -1
  60. package/dist/esm/src/platforms/vtex/utils/auth.js +25 -1
  61. package/dist/esm/src/platforms/vtex/utils/auth.js.map +1 -1
  62. package/dist/esm/src/platforms/vtex/utils/cookies.d.ts.map +1 -1
  63. package/dist/esm/src/platforms/vtex/utils/cookies.js +36 -3
  64. package/dist/esm/src/platforms/vtex/utils/cookies.js.map +1 -1
  65. package/dist/esm/src/typeDefs/query.graphql +21 -16
  66. package/dist/esm/src/typeDefs/session.graphql +2 -0
  67. package/package.json +2 -2
  68. package/src/__generated__/schema.ts +2 -0
  69. package/src/directives/auth.ts +34 -0
  70. package/src/index.ts +7 -2
  71. package/src/platforms/vtex/clients/commerce/index.ts +1 -1
  72. package/src/platforms/vtex/clients/commerce/types/Session.ts +1 -0
  73. package/src/platforms/vtex/resolvers/query.ts +14 -25
  74. package/src/platforms/vtex/resolvers/validateSession.ts +26 -6
  75. package/src/platforms/vtex/utils/auth.ts +35 -1
  76. package/src/platforms/vtex/utils/cookies.ts +42 -3
  77. package/src/typeDefs/query.graphql +21 -16
  78. package/src/typeDefs/session.graphql +2 -0
@@ -426,7 +426,7 @@ export const VtexCommerce = (
426
426
 
427
427
  params.set(
428
428
  'items',
429
- 'profile.id,profile.email,profile.firstName,profile.lastName,shopper.firstName,shopper.lastName,store.channel,store.countryCode,store.cultureInfo,store.currencyCode,store.currencySymbol,authentication.customerId,authentication.storeUserId,authentication.storeUserEmail,authentication.unitId,authentication.unitName,checkout.regionId,public.postalCode'
429
+ 'profile.id,profile.email,profile.firstName,profile.lastName,shopper.firstName,shopper.lastName,shopper.organizationManager,store.channel,store.countryCode,store.cultureInfo,store.currencyCode,store.currencySymbol,authentication.customerId,authentication.storeUserId,authentication.storeUserEmail,authentication.unitId,authentication.unitName,checkout.regionId,public.postalCode'
430
430
  )
431
431
 
432
432
  const headers: HeadersInit = withCookie({
@@ -42,6 +42,7 @@ export interface Profile {
42
42
  export interface Shopper {
43
43
  firstName?: Value
44
44
  lastName?: Value
45
+ organizationManager?: { value: boolean }
45
46
  }
46
47
 
47
48
  export interface Checkout {
@@ -424,14 +424,15 @@ export const Query = {
424
424
  { orderId }: QueryUserOrderArgs,
425
425
  ctx: Context
426
426
  ) => {
427
- const {
428
- clients: { commerce },
429
- } = ctx
430
- if (!orderId) {
431
- throw new BadRequestError('Missing orderId')
432
- }
433
-
434
427
  try {
428
+ if (!orderId) {
429
+ throw new BadRequestError('Missing orderId')
430
+ }
431
+
432
+ const {
433
+ clients: { commerce },
434
+ } = ctx
435
+
435
436
  const order = await commerce.oms.userOrder({ orderId })
436
437
 
437
438
  if (!order) {
@@ -530,6 +531,7 @@ export const Query = {
530
531
  } = ctx
531
532
 
532
533
  const orders = await commerce.oms.listUserOrders(filters)
534
+
533
535
  return {
534
536
  list: orders.list?.map((order: UserOrderFromList) => ({
535
537
  orderId: order.orderId,
@@ -546,23 +548,11 @@ export const Query = {
546
548
  paging: orders.paging,
547
549
  }
548
550
  },
549
- validateUser: async (_: unknown, __: unknown, ctx: Context) => {
550
- const {
551
- clients: { commerce },
552
- } = ctx
553
-
554
- // This resolver is used to validate if the user is logged in
555
- // and has access to the account area.
556
- // If the user is not logged in, it will throw an error.
557
- // If the user is logged in, it will return true.
558
- try {
559
- const response = await commerce.vtexid.validate()
560
-
561
- return {
562
- isValid: response.authStatus === 'Success',
563
- }
564
- } catch (error) {
565
- throw new ForbiddenError('You are not allowed to access this resource')
551
+ validateUser: async (_: unknown, __: unknown, _ctx: Context) => {
552
+ // Authentication is now handled by @auth directive
553
+ // If we reach here, validation was successful, otherwise an error would have been thrown
554
+ return {
555
+ isValid: true,
566
556
  }
567
557
  },
568
558
  // only b2b users
@@ -571,7 +561,6 @@ export const Query = {
571
561
  clients: { commerce },
572
562
  } = ctx
573
563
 
574
- // const params = new URLSearchParams()
575
564
  const sessionData = await commerce.session('').catch(() => null)
576
565
 
577
566
  const shopper = sessionData?.namespaces.shopper ?? null
@@ -97,9 +97,22 @@ export const validateSession = async (
97
97
 
98
98
  const jwt = parseJwt(getAuthCookie(headers?.cookie ?? '', account))
99
99
 
100
- const isRepresentative = jwt?.isRepresentative
101
- const customerId = jwt?.customerId
102
- const unitId = jwt?.unitId
100
+ // Validate JWT token if it exists
101
+ let isValidJwt = false
102
+ if (jwt) {
103
+ try {
104
+ const vtexIdResponse = await clients.commerce.vtexid.validate()
105
+ isValidJwt = vtexIdResponse?.authStatus?.toLowerCase() === 'success'
106
+ } catch (error) {
107
+ console.warn('JWT validation failed:', error)
108
+ isValidJwt = false
109
+ }
110
+ }
111
+
112
+ // Only use JWT data if the token is valid
113
+ const isRepresentative = isValidJwt ? jwt?.isRepresentative : false
114
+ const customerId = isValidJwt ? jwt?.customerId : undefined
115
+ const unitId = isValidJwt ? jwt?.unitId : undefined
103
116
 
104
117
  const sessionData = await clients.commerce
105
118
  .session(params.toString())
@@ -164,13 +177,20 @@ export const validateSession = async (
164
177
  customerId: authentication?.customerId?.value ?? customerId ?? '',
165
178
  unitName: authentication?.unitName?.value ?? '',
166
179
  unitId: authentication?.unitId?.value ?? unitId ?? '',
167
- firstName: shopper?.firstName?.value ?? '',
168
- lastName: shopper?.lastName?.value ?? '',
180
+ firstName:
181
+ typeof shopper?.firstName?.value === 'string'
182
+ ? shopper.firstName.value
183
+ : '',
184
+ lastName:
185
+ typeof shopper?.lastName?.value === 'string'
186
+ ? shopper.lastName.value
187
+ : '',
169
188
  userName:
170
- `${shopper?.firstName?.value ?? ''} ${shopper?.lastName?.value ?? ''}`.trim(),
189
+ `${typeof shopper?.firstName?.value === 'string' ? shopper.firstName.value : ''} ${typeof shopper?.lastName?.value === 'string' ? shopper.lastName.value : ''}`.trim(),
171
190
  userEmail: authentication?.storeUserEmail.value ?? '',
172
191
  savedPostalCode: publicData?.postalCode?.value ?? '',
173
192
  contractName: contract?.corporateName ?? '',
193
+ organizationManager: shopper?.organizationManager?.value ?? false,
174
194
  }
175
195
  : null,
176
196
  marketingData,
@@ -1,4 +1,5 @@
1
- import { ForbiddenError } from '../../..'
1
+ import { ForbiddenError, UnauthorizedError } from '../../..'
2
+ import type { Context } from '../index'
2
3
 
3
4
  /**
4
5
  * Creates a function that adds VTEX API AppKey and AppToken headers to requests.
@@ -25,3 +26,36 @@ export const getWithAppKeyAndToken = () => {
25
26
  }
26
27
  }
27
28
  }
29
+
30
+ /**
31
+ * Utility function to validate user authentication
32
+ * Centralized validation logic for all account-related resolvers
33
+ */
34
+ export const validateUserAuthentication = async (
35
+ ctx: Context
36
+ ): Promise<void> => {
37
+ const {
38
+ clients: { commerce },
39
+ } = ctx
40
+
41
+ try {
42
+ const validation = await commerce.vtexid.validate()
43
+
44
+ if (validation?.authStatus?.toLowerCase() !== 'success') {
45
+ throw new UnauthorizedError('Authentication required')
46
+ }
47
+ } catch (error) {
48
+ const status = (error as any).extensions?.status ?? (error as any).status
49
+
50
+ if (status === 401) {
51
+ throw new UnauthorizedError('Authentication required')
52
+ }
53
+
54
+ if (status === 403) {
55
+ throw new ForbiddenError('You are not allowed to access this resource')
56
+ }
57
+
58
+ // For any other error, throw UnauthorizedError as it likely needs token refresh
59
+ throw new UnauthorizedError('Authentication required')
60
+ }
61
+ }
@@ -6,6 +6,42 @@ export interface ContextForCookies {
6
6
  storage: Pick<Context['storage'], 'cookies'>
7
7
  }
8
8
 
9
+ /**
10
+ * Normalizes cookie string by removing duplicates and keeping the last value for each key
11
+ * Example: "key1=value1; key2=value2; key1=value3" -> "key2=value2; key1=value3"
12
+ */
13
+ const normalizeCookies = (cookieString: string): string => {
14
+ if (!cookieString) {
15
+ return cookieString
16
+ }
17
+
18
+ const cookieMap = new Map<string, string>()
19
+ const cookies = cookieString.split(';')
20
+ const processedKeys: string[] = []
21
+
22
+ // Process cookies to build map with last values and track order
23
+ cookies.forEach((cookie) => {
24
+ const trimmedCookie = cookie.trim()
25
+ if (trimmedCookie) {
26
+ const equalIndex = trimmedCookie.indexOf('=')
27
+ if (equalIndex > 0) {
28
+ const key = trimmedCookie.substring(0, equalIndex)
29
+ const value = trimmedCookie.substring(equalIndex + 1)
30
+
31
+ // If this is the first time we see this key, record its position
32
+ if (!cookieMap.has(key)) {
33
+ processedKeys.push(key)
34
+ }
35
+
36
+ cookieMap.set(key, value)
37
+ }
38
+ }
39
+ })
40
+
41
+ // Rebuild cookie string maintaining the order of first appearance
42
+ return processedKeys.map((key) => `${key}=${cookieMap.get(key)}`).join('; ')
43
+ }
44
+
9
45
  const MATCH_FIRST_SET_COOKIE_KEY_VALUE = /^([^=]+)=([^;]*)/
10
46
 
11
47
  /**
@@ -64,10 +100,12 @@ export const getUpdatedCookie = (ctx: ContextForCookies) => {
64
100
  return null
65
101
  }
66
102
 
103
+ // Normalize cookies to handle duplicates (keep last value)
104
+ const normalizedCookie = normalizeCookies(ctx.headers.cookie)
67
105
  const contextStorageCookies = Array.from(ctx.storage.cookies.entries())
68
106
 
69
107
  if (contextStorageCookies.length === 0) {
70
- return ctx.headers.cookie
108
+ return normalizedCookie
71
109
  }
72
110
 
73
111
  return contextStorageCookies.reduce(
@@ -77,7 +115,7 @@ export const getUpdatedCookie = (ctx: ContextForCookies) => {
77
115
  storageCookieKey,
78
116
  storageCookieValue
79
117
  ),
80
- ctx.headers.cookie
118
+ normalizedCookie
81
119
  )
82
120
  }
83
121
 
@@ -98,7 +136,8 @@ export const getWithCookie = (ctx: ContextForCookies) =>
98
136
  }
99
137
 
100
138
  export const getAuthCookie = (cookies: string, account: string) => {
101
- const parsedCookies = parse(cookies)
139
+ const normalizedCookies = normalizeCookies(cookies)
140
+ const parsedCookies = parse(normalizedCookies)
102
141
  const authCookie = parsedCookies[`VtexIdclientAutCookie_${account}`]
103
142
  return authCookie || ''
104
143
  }
@@ -216,7 +216,7 @@ type Query {
216
216
  """
217
217
  locator: [IStoreSelectedFacet!]!
218
218
  ): StoreProduct!
219
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
219
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
220
220
 
221
221
  """
222
222
  Returns the details of a collection based on the collection slug.
@@ -227,7 +227,7 @@ type Query {
227
227
  """
228
228
  slug: String!
229
229
  ): StoreCollection!
230
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
230
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
231
231
 
232
232
  """
233
233
  Returns the result of a product, facet, or suggestion search.
@@ -258,7 +258,7 @@ type Query {
258
258
  """
259
259
  sponsoredCount: Int
260
260
  ): StoreSearchResult!
261
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
261
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
262
262
 
263
263
  """
264
264
  Returns information about all products.
@@ -273,13 +273,13 @@ type Query {
273
273
  """
274
274
  after: String
275
275
  ): StoreProductConnection!
276
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
276
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
277
277
 
278
278
  """
279
279
  Returns information about selected products.
280
280
  """
281
281
  products(productIds: [String!]!): [StoreProduct!]!
282
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
282
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
283
283
 
284
284
  """
285
285
  Returns information about all collections.
@@ -294,7 +294,7 @@ type Query {
294
294
  """
295
295
  after: String
296
296
  ): StoreCollectionConnection!
297
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
297
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
298
298
 
299
299
  """
300
300
  Returns information about shipping simulation.
@@ -313,7 +313,7 @@ type Query {
313
313
  """
314
314
  country: String!
315
315
  ): ShippingData
316
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
316
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
317
317
 
318
318
  """
319
319
  Returns if there's a redirect for a search.
@@ -350,7 +350,7 @@ type Query {
350
350
  """
351
351
  salesChannel: String
352
352
  ): SellersData
353
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
353
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
354
354
 
355
355
  """
356
356
  Returns information about the profile.
@@ -361,7 +361,7 @@ type Query {
361
361
  """
362
362
  id: String!
363
363
  ): Profile
364
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
364
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
365
365
 
366
366
  """
367
367
  Returns the total product count information based on a specific location accessible through the VTEX segment cookie.
@@ -372,7 +372,7 @@ type Query {
372
372
  """
373
373
  term: String
374
374
  ): ProductCountResult
375
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
375
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
376
376
 
377
377
  """
378
378
  Returns information about the Details of an User Order.
@@ -383,7 +383,8 @@ type Query {
383
383
  """
384
384
  orderId: String!
385
385
  ): UserOrderResult
386
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
386
+ @auth
387
+ @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
387
388
 
388
389
  """
389
390
  Returns information about the list of Orders that the User can view.
@@ -418,25 +419,29 @@ type Query {
418
419
  """
419
420
  clientEmail: String
420
421
  ): UserOrderListMinimalResult
421
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
422
+ @auth
423
+ @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
422
424
 
423
425
  """
424
426
  Returns information about the current user details.
425
427
  """
426
428
  userDetails: StoreUserDetails!
427
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
429
+ @auth
430
+ @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
428
431
 
429
432
  """
430
433
  Returns the account profile information for the current authenticated user (b2b or b2c user).
431
434
  """
432
435
  accountProfile: StoreAccountProfile!
433
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
436
+ @auth
437
+ @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
434
438
 
435
439
  """
436
440
  Returns information about the user validation.
437
441
  """
438
442
  validateUser: ValidateUserData
439
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
443
+ @auth
444
+ @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
440
445
 
441
446
  """
442
447
  Returns a list of pickup points near to the given geo coordinates.
@@ -447,7 +452,7 @@ type Query {
447
452
  """
448
453
  geoCoordinates: IStoreGeoCoordinates
449
454
  ): PickupPoints
450
- @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
455
+ @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
451
456
  }
452
457
 
453
458
  type ValidateUserData {
@@ -199,6 +199,7 @@ type StoreB2B {
199
199
  userEmail: String
200
200
  savedPostalCode: String
201
201
  contractName: String
202
+ organizationManager: Boolean
202
203
  }
203
204
 
204
205
  input IStoreB2B {
@@ -212,6 +213,7 @@ input IStoreB2B {
212
213
  userEmail: String
213
214
  savedPostalCode: String
214
215
  contractName: String
216
+ organizationManager: Boolean
215
217
  }
216
218
 
217
219
  """