@shopify/shopify-api 14.0.0 → 14.0.1

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.
@@ -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
  );
@@ -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 = '14.0.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopify/shopify-api",
3
- "version": "14.0.0",
3
+ "version": "14.0.1",
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",