@shopify/shopify-api 14.0.0 → 15.0.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 (41) hide show
  1. package/dist/cjs/lib/clients/common.js +64 -8
  2. package/dist/cjs/lib/clients/common.js.map +1 -1
  3. package/dist/cjs/lib/types.js +0 -2
  4. package/dist/cjs/lib/types.js.map +1 -1
  5. package/dist/cjs/lib/utils/fetch-request.js +12 -1
  6. package/dist/cjs/lib/utils/fetch-request.js.map +1 -1
  7. package/dist/cjs/lib/version.js +1 -1
  8. package/dist/cjs/lib/version.js.map +1 -1
  9. package/dist/cjs/lib/webhooks/types.js +0 -2
  10. package/dist/cjs/lib/webhooks/types.js.map +1 -1
  11. package/dist/cjs/lib/webhooks/validate.js +0 -5
  12. package/dist/cjs/lib/webhooks/validate.js.map +1 -1
  13. package/dist/cjs/tsconfig.build.tsbuildinfo +1 -1
  14. package/dist/esm/lib/clients/common.mjs +64 -8
  15. package/dist/esm/lib/clients/common.mjs.map +1 -1
  16. package/dist/esm/lib/types.mjs +0 -2
  17. package/dist/esm/lib/types.mjs.map +1 -1
  18. package/dist/esm/lib/utils/fetch-request.mjs +12 -1
  19. package/dist/esm/lib/utils/fetch-request.mjs.map +1 -1
  20. package/dist/esm/lib/version.mjs +1 -1
  21. package/dist/esm/lib/version.mjs.map +1 -1
  22. package/dist/esm/lib/webhooks/types.mjs +0 -2
  23. package/dist/esm/lib/webhooks/types.mjs.map +1 -1
  24. package/dist/esm/lib/webhooks/validate.mjs +0 -5
  25. package/dist/esm/lib/webhooks/validate.mjs.map +1 -1
  26. package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
  27. package/dist/ts/lib/clients/common.d.ts.map +1 -1
  28. package/dist/ts/lib/types.d.ts +0 -2
  29. package/dist/ts/lib/types.d.ts.map +1 -1
  30. package/dist/ts/lib/utils/fetch-request.d.ts.map +1 -1
  31. package/dist/ts/lib/version.d.ts +1 -1
  32. package/dist/ts/lib/webhooks/types.d.ts +0 -4
  33. package/dist/ts/lib/webhooks/types.d.ts.map +1 -1
  34. package/dist/ts/tsconfig.build.tsbuildinfo +1 -1
  35. package/lib/clients/common.ts +91 -7
  36. package/lib/types.ts +0 -2
  37. package/lib/utils/fetch-request.ts +13 -1
  38. package/lib/version.ts +1 -1
  39. package/lib/webhooks/types.ts +0 -4
  40. package/lib/webhooks/validate.ts +0 -9
  41. package/package.json +1 -1
@@ -25,6 +25,86 @@ export function getUserAgent(config: ConfigInterface): string {
25
25
  return userAgentPrefix;
26
26
  }
27
27
 
28
+ const REDACTED_LOG_VALUE = '****';
29
+
30
+ // These headers carry reusable credentials and must not reach the logs.
31
+ const SENSITIVE_LOG_HEADERS = new Set([
32
+ 'authorization',
33
+ 'cookie',
34
+ 'set-cookie',
35
+ 'shopify-storefront-private-token',
36
+ 'x-shopify-access-token',
37
+ 'x-shopify-storefront-access-token',
38
+ ]);
39
+
40
+ function sanitizeHeaderValue(name: string, value: unknown): unknown {
41
+ return SENSITIVE_LOG_HEADERS.has(name.toLowerCase())
42
+ ? REDACTED_LOG_VALUE
43
+ : value;
44
+ }
45
+
46
+ // Keep the original serialized shape of request headers. A Headers instance
47
+ // has no own enumerable properties, so it stays opaque here, the same as
48
+ // JSON.stringify shows it.
49
+ function sanitizeHeaders(headers: any): any {
50
+ if (!headers || typeof headers !== 'object') {
51
+ return headers;
52
+ }
53
+
54
+ if (Array.isArray(headers)) {
55
+ return headers.map((header) =>
56
+ Array.isArray(header)
57
+ ? [header[0], sanitizeHeaderValue(header[0], header[1])]
58
+ : header,
59
+ );
60
+ }
61
+
62
+ return Object.fromEntries(
63
+ Object.entries(headers).map(([name, value]) => [
64
+ name,
65
+ sanitizeHeaderValue(name, value),
66
+ ]),
67
+ );
68
+ }
69
+
70
+ // Response headers were always enumerated in the log, so keep that and
71
+ // redact the credential values.
72
+ function sanitizeEnumerableHeaders(headers: any): any {
73
+ if (typeof headers?.entries === 'function' && !Array.isArray(headers)) {
74
+ return Object.fromEntries(
75
+ [...headers.entries()].map(([name, value]: [string, string]) => [
76
+ name,
77
+ sanitizeHeaderValue(name, value),
78
+ ]),
79
+ );
80
+ }
81
+
82
+ return sanitizeHeaders(headers);
83
+ }
84
+
85
+ function sanitizeRequestParams<T>(requestParams: T): T {
86
+ if (Array.isArray(requestParams)) {
87
+ return requestParams.map((param) =>
88
+ param && typeof param === 'object' && 'headers' in param
89
+ ? {...param, headers: sanitizeHeaders(param.headers)}
90
+ : param,
91
+ ) as T;
92
+ }
93
+
94
+ if (
95
+ requestParams &&
96
+ typeof requestParams === 'object' &&
97
+ 'headers' in requestParams
98
+ ) {
99
+ return {
100
+ ...requestParams,
101
+ headers: sanitizeHeaders((requestParams as {headers: any}).headers),
102
+ };
103
+ }
104
+
105
+ return requestParams;
106
+ }
107
+
28
108
  function serializeResponse(response: Response | any) {
29
109
  if (!response) {
30
110
  return {error: 'No response object provided'};
@@ -42,10 +122,8 @@ function serializeResponse(response: Response | any) {
42
122
  url,
43
123
  };
44
124
 
45
- if (headers?.entries) {
46
- serialized.headers = Object.fromEntries(headers.entries());
47
- } else if (headers) {
48
- serialized.headers = headers;
125
+ if (headers) {
126
+ serialized.headers = sanitizeEnumerableHeaders(headers);
49
127
  }
50
128
 
51
129
  return serialized;
@@ -61,7 +139,9 @@ export function clientLoggerFactory(config: ConfigInterface) {
61
139
  case 'HTTP-Response': {
62
140
  const responseLog: HTTPResponseLog['content'] = logContent.content;
63
141
  logger(config).debug('Received response for HTTP request', {
64
- requestParams: JSON.stringify(responseLog.requestParams),
142
+ requestParams: JSON.stringify(
143
+ sanitizeRequestParams(responseLog.requestParams),
144
+ ),
65
145
  response: JSON.stringify(serializeResponse(responseLog.response)),
66
146
  });
67
147
  break;
@@ -69,7 +149,9 @@ export function clientLoggerFactory(config: ConfigInterface) {
69
149
  case 'HTTP-Retry': {
70
150
  const responseLog: HTTPRetryLog['content'] = logContent.content;
71
151
  logger(config).debug('Retrying HTTP request', {
72
- requestParams: JSON.stringify(responseLog.requestParams),
152
+ requestParams: JSON.stringify(
153
+ sanitizeRequestParams(responseLog.requestParams),
154
+ ),
73
155
  retryAttempt: responseLog.retryAttempt,
74
156
  maxRetries: responseLog.maxRetries,
75
157
  response: responseLog.lastResponse
@@ -84,7 +166,9 @@ export function clientLoggerFactory(config: ConfigInterface) {
84
166
  logger(config).debug(
85
167
  'Received response containing Deprecated GraphQL Notice',
86
168
  {
87
- requestParams: JSON.stringify(responseLog.requestParams),
169
+ requestParams: JSON.stringify(
170
+ sanitizeRequestParams(responseLog.requestParams),
171
+ ),
88
172
  deprecationNotice: responseLog.deprecationNotice,
89
173
  },
90
174
  );
package/lib/types.ts CHANGED
@@ -42,11 +42,9 @@ export const ShopifyEventsHeader = {
42
42
  Topic: 'shopify-topic',
43
43
  Domain: 'shopify-shop-domain',
44
44
  ApiVersion: 'shopify-api-version',
45
- EventId: 'shopify-event-id',
46
45
  WebhookId: 'shopify-webhook-id',
47
46
  Handle: 'shopify-handle',
48
47
  Action: 'shopify-action',
49
- ResourceId: 'shopify-resource-id',
50
48
  TriggeredAt: 'shopify-triggered-at',
51
49
  } as const;
52
50
 
@@ -3,6 +3,18 @@ import {LogSeverity} from '../types';
3
3
  import {abstractFetch} from '../../runtime';
4
4
  import {ConfigInterface} from '../base-types';
5
5
 
6
+ const OAUTH_TOKEN_ENDPOINT_PATH = '/admin/oauth/access_token';
7
+
8
+ // Token endpoint bodies carry OAuth secrets, so they must not reach the logs.
9
+ // When the URL does not parse, fail closed and keep the body out of the logs.
10
+ function canLogRequestBody(url: string): boolean {
11
+ try {
12
+ return new URL(url).pathname !== OAUTH_TOKEN_ENDPOINT_PATH;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
6
18
  export function fetchRequestFactory(config: ConfigInterface) {
7
19
  return async function fetchRequest(
8
20
  url: string,
@@ -16,7 +28,7 @@ export function fetchRequestFactory(config: ConfigInterface) {
16
28
  log.debug('Making HTTP request', {
17
29
  method: options?.method || 'GET',
18
30
  url,
19
- ...(options?.body && {body: options?.body}),
31
+ ...(options?.body && canLogRequestBody(url) && {body: options.body}),
20
32
  });
21
33
  }
22
34
 
package/lib/version.ts CHANGED
@@ -1 +1 @@
1
- export const SHOPIFY_API_LIBRARY_VERSION = '14.0.0';
1
+ export const SHOPIFY_API_LIBRARY_VERSION = '15.0.0';
@@ -27,10 +27,8 @@ export const WEBHOOK_HEADER_NAMES = {
27
27
  domain: ShopifyEventsHeader.Domain,
28
28
  apiVersion: ShopifyEventsHeader.ApiVersion,
29
29
  webhookId: ShopifyEventsHeader.WebhookId,
30
- eventId: ShopifyEventsHeader.EventId,
31
30
  handle: ShopifyEventsHeader.Handle,
32
31
  action: ShopifyEventsHeader.Action,
33
- resourceId: ShopifyEventsHeader.ResourceId,
34
32
  triggeredAt: ShopifyEventsHeader.TriggeredAt,
35
33
  },
36
34
  } as const;
@@ -187,10 +185,8 @@ export interface WebhooksWebhookFields extends BaseWebhookFields {
187
185
  export interface EventsWebhookFields extends BaseWebhookFields {
188
186
  webhookType: typeof WebhookType.Events;
189
187
  webhookId: string;
190
- eventId: string;
191
188
  handle?: string;
192
189
  action?: string;
193
- resourceId?: string;
194
190
  }
195
191
 
196
192
  export type WebhookFields = WebhooksWebhookFields | EventsWebhookFields;
@@ -164,11 +164,6 @@ function checkEventsHeaders(
164
164
  headerNames.webhookId,
165
165
  missingHeaders,
166
166
  );
167
- const eventId = getRequiredHeader(
168
- headers,
169
- headerNames.eventId,
170
- missingHeaders,
171
- );
172
167
 
173
168
  if (missingHeaders.length) {
174
169
  return {
@@ -185,7 +180,6 @@ function checkEventsHeaders(
185
180
  domain: domain!,
186
181
  apiVersion: apiVersion!,
187
182
  webhookId: webhookId!,
188
- eventId: eventId!,
189
183
  };
190
184
 
191
185
  const handle = getHeader(headers, headerNames.handle);
@@ -194,9 +188,6 @@ function checkEventsHeaders(
194
188
  const action = getHeader(headers, headerNames.action);
195
189
  if (action) fields.action = action;
196
190
 
197
- const resourceId = getHeader(headers, headerNames.resourceId);
198
- if (resourceId) fields.resourceId = resourceId;
199
-
200
191
  const triggeredAt = getHeader(headers, headerNames.triggeredAt);
201
192
  if (triggeredAt) fields.triggeredAt = triggeredAt;
202
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopify/shopify-api",
3
- "version": "14.0.0",
3
+ "version": "15.0.0",
4
4
  "description": "Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks",
5
5
  "types": "./dist/ts/lib/index.d.ts",
6
6
  "module": "./dist/esm/lib/index.mjs",