@commercetools-backend/express 0.0.0-CRAFT-1791-20251006162610

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) commercetools GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @commercetools-backend/express
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/@commercetools-backend/express"><img src="https://badgen.net/npm/v/@commercetools-backend/express" alt="Latest release (latest dist-tag)" /></a> <a href="https://www.npmjs.com/package/@commercetools-backend/express"><img src="https://badgen.net/npm/v/@commercetools-backend/express/next" alt="Latest release (next dist-tag)" /></a> <a href="https://bundlephobia.com/result?p=@commercetools-backend/express"><img src="https://badgen.net/bundlephobia/minzip/@commercetools-backend/express" alt="Minified + GZipped size" /></a> <a href="https://github.com/commercetools/merchant-center-application-kit/blob/main/LICENSE"><img src="https://badgen.net/github/license/commercetools/merchant-center-application-kit" alt="GitHub license" /></a>
5
+ </p>
6
+
7
+ Zero-config HTTP server as Express.js to facilitate development.
8
+
9
+ This package is primarily built for HTTP servers used by Custom Applications and it provides a set of components to facilitate the development of the HTTP server.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ $ npm install --save @commercetools-backend/express
15
+ ```
16
+
17
+ Check out the [documentation](https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api) for more information.
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWVyY2V0b29scy1iYWNrZW5kLWV4cHJlc3MuY2pzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvaW5kZXguZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _URL = require('@babel/runtime-corejs3/core-js-stable/url');
6
+ var _Map = require('@babel/runtime-corejs3/core-js-stable/map');
7
+ var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
8
+ var jose = require('jose');
9
+ var _slicedToArray = require('@babel/runtime-corejs3/helpers/slicedToArray');
10
+ var _findInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/find');
11
+ var _Object$entries = require('@babel/runtime-corejs3/core-js-stable/object/entries');
12
+ var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array');
13
+
14
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
15
+
16
+ var _URL__default = /*#__PURE__*/_interopDefault(_URL);
17
+ var _Map__default = /*#__PURE__*/_interopDefault(_Map);
18
+ var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
19
+ var _findInstanceProperty__default = /*#__PURE__*/_interopDefault(_findInstanceProperty);
20
+ var _Object$entries__default = /*#__PURE__*/_interopDefault(_Object$entries);
21
+ var _Array$isArray__default = /*#__PURE__*/_interopDefault(_Array$isArray);
22
+
23
+ const CLOUD_IDENTIFIERS = {
24
+ GCP_AU: 'gcp-au',
25
+ GCP_EU: 'gcp-eu',
26
+ GCP_US: 'gcp-us',
27
+ AWS_EU: 'aws-eu',
28
+ AWS_US: 'aws-us',
29
+ /**
30
+ * @deprecated: use `AWS_EU` instead
31
+ */
32
+ AWS_FRA: 'aws-fra',
33
+ /**
34
+ * @deprecated: use `AWS_US` instead
35
+ */
36
+ AWS_OHIO: 'aws-ohio'
37
+ };
38
+ const MC_API_URLS = {
39
+ [CLOUD_IDENTIFIERS.GCP_AU]: 'https://mc-api.australia-southeast1.gcp.commercetools.com',
40
+ [CLOUD_IDENTIFIERS.GCP_EU]: 'https://mc-api.europe-west1.gcp.commercetools.com',
41
+ [CLOUD_IDENTIFIERS.GCP_US]: 'https://mc-api.us-central1.gcp.commercetools.com',
42
+ [CLOUD_IDENTIFIERS.AWS_EU]: 'https://mc-api.eu-central-1.aws.commercetools.com',
43
+ [CLOUD_IDENTIFIERS.AWS_US]: 'https://mc-api.us-east-2.aws.commercetools.com',
44
+ /**
45
+ * @deprecated: use `AWS_EU` instead
46
+ */
47
+ [CLOUD_IDENTIFIERS.AWS_FRA]: 'https://mc-api.eu-central-1.aws.commercetools.com',
48
+ /**
49
+ * @deprecated: use `AWS_US` instead
50
+ */
51
+ [CLOUD_IDENTIFIERS.AWS_OHIO]: 'https://mc-api.us-east-2.aws.commercetools.com'
52
+ };
53
+ const MC_API_PROXY_HEADERS = {
54
+ FORWARD_TO_VERSION: 'x-mc-api-forward-to-version',
55
+ CLOUD_IDENTIFIER: 'x-mc-api-cloud-identifier'
56
+ };
57
+
58
+ const getHeaderByCaseInsensitiveKey = (headers, headerKey) => {
59
+ var _context;
60
+ const matchingHeader = _findInstanceProperty__default["default"](_context = _Object$entries__default["default"](headers)).call(_context, _ref => {
61
+ let _ref2 = _slicedToArray(_ref, 1),
62
+ key = _ref2[0];
63
+ return headerKey.toLowerCase() === key.toLowerCase();
64
+ });
65
+ if (matchingHeader && matchingHeader.length > 0) {
66
+ const _matchingHeader = _slicedToArray(matchingHeader, 2),
67
+ headerValue = _matchingHeader[1];
68
+ return _Array$isArray__default["default"](headerValue) ? headerValue[0] : headerValue;
69
+ }
70
+ return undefined;
71
+ };
72
+ const getFirstHeaderValueOrThrow = (headers, headerKey, errorMessage) => {
73
+ const headerValue = getHeaderByCaseInsensitiveKey(headers, headerKey);
74
+ if (!headerValue) {
75
+ throw new Error(errorMessage);
76
+ }
77
+ return headerValue;
78
+ };
79
+
80
+ // Assign a session object to the request object.
81
+ const writeSessionContext = (request, verifiedToken) => {
82
+ const publicClaimForProjectKey = `${verifiedToken.iss}/claims/project_key`;
83
+ const publicClaimForUserPermissionsKey = `${verifiedToken.iss}/claims/user_permissions`;
84
+ request.session = {
85
+ userId: verifiedToken.sub,
86
+ projectKey: verifiedToken[publicClaimForProjectKey]
87
+ };
88
+ const userPermissions = verifiedToken[publicClaimForUserPermissionsKey];
89
+ if (Boolean(userPermissions?.length)) {
90
+ request.session.userPermissions = userPermissions;
91
+ }
92
+ };
93
+
94
+ // Given a cloud identifier, try to map it to one of the supported
95
+ // environments and return the MC API URL for that environment.
96
+ // The URL points to the new hostnames.
97
+ // https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames
98
+ const mapCloudIdentifierToIssuer = issuer => {
99
+ switch (issuer) {
100
+ case CLOUD_IDENTIFIERS.GCP_AU:
101
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_AU];
102
+ case CLOUD_IDENTIFIERS.GCP_EU:
103
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_EU];
104
+ case CLOUD_IDENTIFIERS.GCP_US:
105
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_US];
106
+ case CLOUD_IDENTIFIERS.AWS_EU:
107
+ case CLOUD_IDENTIFIERS.AWS_FRA:
108
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_EU];
109
+ case CLOUD_IDENTIFIERS.AWS_US:
110
+ case CLOUD_IDENTIFIERS.AWS_OHIO:
111
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_US];
112
+ default:
113
+ return undefined;
114
+ }
115
+ };
116
+ // Given a cloud identifier, try to map it to a legacy hostname.
117
+ // This is for backwards compatibility.
118
+ const mapToLegacyIssuer = cloudIdentifier => {
119
+ switch (cloudIdentifier) {
120
+ case CLOUD_IDENTIFIERS.GCP_EU:
121
+ return 'https://mc-api.commercetools.com';
122
+ case CLOUD_IDENTIFIERS.GCP_US:
123
+ return 'https://mc-api.commercetools.co';
124
+ default:
125
+ return undefined;
126
+ }
127
+ };
128
+ // Verifies that the issuer is a valid URL.
129
+ const throwIfIssuerIsNotAValidUrl = issuer => {
130
+ try {
131
+ new _URL__default["default"](issuer);
132
+ } catch (error) {
133
+ throw new Error(`Invalid issuer URL "${issuer}". Expected a valid URL to the Merchant Center API Gateway, or a cloud identifier to one of the available cloud regions. See https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames.`);
134
+ }
135
+ };
136
+ // Validates required option values.
137
+ const validateRequiredValues = options => {
138
+ if (!options.audience) {
139
+ throw new Error(`Missing required option "audience"`);
140
+ }
141
+ if (!options.issuer) {
142
+ throw new Error(`Missing required option "issuer"`);
143
+ }
144
+ };
145
+ // Attempt to parse the given issuer. If the value is a cloud identifier, it will
146
+ // be mapped to one of the supported values. If not, we assume the value is a valid URL.
147
+ const getConfiguredDefaultIssuer = options => {
148
+ const issuer = mapCloudIdentifierToIssuer(options.issuer);
149
+ if (!issuer) {
150
+ throwIfIssuerIsNotAValidUrl(options.issuer);
151
+ return options.issuer;
152
+ }
153
+ return issuer;
154
+ };
155
+
156
+ // Construct the audience from the given option + the request path.
157
+ // If the request path is `/`, do not append it to the audience, otherwise
158
+ // the token validation might fail because of mismatching audiences.
159
+ const getConfiguredAudience = (options, requestPath) => {
160
+ // remove the trailing slash
161
+ const url = new _URL__default["default"](`${options.audience.replace(/\/?$/, '')}${requestPath}`);
162
+ switch (options.audiencePolicy) {
163
+ case 'forward-url-origin':
164
+ return url.origin;
165
+ default:
166
+ {
167
+ if (requestPath === '/') {
168
+ return url.origin;
169
+ }
170
+ return `${url.origin}${url.pathname}`;
171
+ }
172
+ }
173
+ };
174
+ const jwksClientByIssuer = new _Map__default["default"]();
175
+ function getJwksClientByIssuer(issuer) {
176
+ const client = jwksClientByIssuer.get(issuer);
177
+ if (client) {
178
+ return client;
179
+ }
180
+ const newClient = jose.createRemoteJWKSet(new _URL__default["default"](`/.well-known/jwks.json`, issuer));
181
+ jwksClientByIssuer.set(issuer, newClient);
182
+ return newClient;
183
+ }
184
+ function createSessionAuthVerifier(options) {
185
+ validateRequiredValues(options);
186
+ const configuredDefaultIssuer = getConfiguredDefaultIssuer(options);
187
+
188
+ // Returns an async HTTP handler.
189
+ return async request => {
190
+ // Get the cloud identifier header, forwarded by the `/proxy/forward-to` endpoint.
191
+ const cloudIdentifierHeader = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.CLOUD_IDENTIFIER, `Missing "X-MC-API-Cloud-Identifier" header.`);
192
+ let issuer = options.inferIssuer && cloudIdentifierHeader ? mapCloudIdentifierToIssuer(cloudIdentifierHeader) ?? configuredDefaultIssuer : configuredDefaultIssuer;
193
+
194
+ // Get the `Accept-version` header, forwarded by the `/proxy/forward-to` endpoint.
195
+ // The version should be sent by the client making the request, to use the features of v2.
196
+ const proxyForwardVersion = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.FORWARD_TO_VERSION, `Missing "X-MC-API-Forward-To-Version" header.`);
197
+ if (proxyForwardVersion === 'v1') {
198
+ // Fall back to legacy issuer domains
199
+ issuer = mapToLegacyIssuer(cloudIdentifierHeader) ?? issuer;
200
+ }
201
+ const requestUrlPath = options.getRequestUrl ? options.getRequestUrl(request) : request.originalUrl ?? request.url;
202
+ if (!requestUrlPath || !_startsWithInstanceProperty__default["default"](requestUrlPath).call(requestUrlPath, '/')) {
203
+ throw new Error(`Invalid request URI path "${requestUrlPath}". Please make sure that the "request" object has either a property "originalUrl" or "url". If not, you should implement the "getRequestUrl" function and make sure to return a valid URI path value starting with "/". More info at https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api#validating-the-json-web-token`);
204
+ }
205
+ const audience = getConfiguredAudience(options, requestUrlPath);
206
+ const authorizationHeader = request.headers['authorization'];
207
+ if (typeof authorizationHeader !== 'string') {
208
+ throw new Error(`Missing "authorization" header`);
209
+ }
210
+ const exchangeToken = authorizationHeader.replace(/^Bearer (.*)$/, '$1');
211
+ const jwksClient = getJwksClientByIssuer(issuer);
212
+ const verifiedToken = await jose.jwtVerify(exchangeToken, jwksClient, {
213
+ algorithms: ['RS256'],
214
+ audience,
215
+ issuer
216
+ });
217
+ writeSessionContext(request, verifiedToken.payload);
218
+ return;
219
+ };
220
+ }
221
+
222
+ function createSessionMiddleware(options) {
223
+ const sessionAuthVerifier = createSessionAuthVerifier(options);
224
+ return async (request, _response, next) => {
225
+ try {
226
+ await sessionAuthVerifier(request);
227
+ next();
228
+ } catch (error) {
229
+ next(error);
230
+ }
231
+ };
232
+ }
233
+
234
+ exports.CLOUD_IDENTIFIERS = CLOUD_IDENTIFIERS;
235
+ exports.MC_API_URLS = MC_API_URLS;
236
+ exports.createSessionAuthVerifier = createSessionAuthVerifier;
237
+ exports.createSessionMiddleware = createSessionMiddleware;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./commercetools-backend-express.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./commercetools-backend-express.cjs.dev.js");
7
+ }
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _URL = require('@babel/runtime-corejs3/core-js-stable/url');
6
+ var _Map = require('@babel/runtime-corejs3/core-js-stable/map');
7
+ var _startsWithInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/starts-with');
8
+ var jose = require('jose');
9
+ var _slicedToArray = require('@babel/runtime-corejs3/helpers/slicedToArray');
10
+ var _findInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/find');
11
+ var _Object$entries = require('@babel/runtime-corejs3/core-js-stable/object/entries');
12
+ var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array');
13
+
14
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
15
+
16
+ var _URL__default = /*#__PURE__*/_interopDefault(_URL);
17
+ var _Map__default = /*#__PURE__*/_interopDefault(_Map);
18
+ var _startsWithInstanceProperty__default = /*#__PURE__*/_interopDefault(_startsWithInstanceProperty);
19
+ var _findInstanceProperty__default = /*#__PURE__*/_interopDefault(_findInstanceProperty);
20
+ var _Object$entries__default = /*#__PURE__*/_interopDefault(_Object$entries);
21
+ var _Array$isArray__default = /*#__PURE__*/_interopDefault(_Array$isArray);
22
+
23
+ const CLOUD_IDENTIFIERS = {
24
+ GCP_AU: 'gcp-au',
25
+ GCP_EU: 'gcp-eu',
26
+ GCP_US: 'gcp-us',
27
+ AWS_EU: 'aws-eu',
28
+ AWS_US: 'aws-us',
29
+ /**
30
+ * @deprecated: use `AWS_EU` instead
31
+ */
32
+ AWS_FRA: 'aws-fra',
33
+ /**
34
+ * @deprecated: use `AWS_US` instead
35
+ */
36
+ AWS_OHIO: 'aws-ohio'
37
+ };
38
+ const MC_API_URLS = {
39
+ [CLOUD_IDENTIFIERS.GCP_AU]: 'https://mc-api.australia-southeast1.gcp.commercetools.com',
40
+ [CLOUD_IDENTIFIERS.GCP_EU]: 'https://mc-api.europe-west1.gcp.commercetools.com',
41
+ [CLOUD_IDENTIFIERS.GCP_US]: 'https://mc-api.us-central1.gcp.commercetools.com',
42
+ [CLOUD_IDENTIFIERS.AWS_EU]: 'https://mc-api.eu-central-1.aws.commercetools.com',
43
+ [CLOUD_IDENTIFIERS.AWS_US]: 'https://mc-api.us-east-2.aws.commercetools.com',
44
+ /**
45
+ * @deprecated: use `AWS_EU` instead
46
+ */
47
+ [CLOUD_IDENTIFIERS.AWS_FRA]: 'https://mc-api.eu-central-1.aws.commercetools.com',
48
+ /**
49
+ * @deprecated: use `AWS_US` instead
50
+ */
51
+ [CLOUD_IDENTIFIERS.AWS_OHIO]: 'https://mc-api.us-east-2.aws.commercetools.com'
52
+ };
53
+ const MC_API_PROXY_HEADERS = {
54
+ FORWARD_TO_VERSION: 'x-mc-api-forward-to-version',
55
+ CLOUD_IDENTIFIER: 'x-mc-api-cloud-identifier'
56
+ };
57
+
58
+ const getHeaderByCaseInsensitiveKey = (headers, headerKey) => {
59
+ var _context;
60
+ const matchingHeader = _findInstanceProperty__default["default"](_context = _Object$entries__default["default"](headers)).call(_context, _ref => {
61
+ let _ref2 = _slicedToArray(_ref, 1),
62
+ key = _ref2[0];
63
+ return headerKey.toLowerCase() === key.toLowerCase();
64
+ });
65
+ if (matchingHeader && matchingHeader.length > 0) {
66
+ const _matchingHeader = _slicedToArray(matchingHeader, 2),
67
+ headerValue = _matchingHeader[1];
68
+ return _Array$isArray__default["default"](headerValue) ? headerValue[0] : headerValue;
69
+ }
70
+ return undefined;
71
+ };
72
+ const getFirstHeaderValueOrThrow = (headers, headerKey, errorMessage) => {
73
+ const headerValue = getHeaderByCaseInsensitiveKey(headers, headerKey);
74
+ if (!headerValue) {
75
+ throw new Error(errorMessage);
76
+ }
77
+ return headerValue;
78
+ };
79
+
80
+ // Assign a session object to the request object.
81
+ const writeSessionContext = (request, verifiedToken) => {
82
+ const publicClaimForProjectKey = `${verifiedToken.iss}/claims/project_key`;
83
+ const publicClaimForUserPermissionsKey = `${verifiedToken.iss}/claims/user_permissions`;
84
+ request.session = {
85
+ userId: verifiedToken.sub,
86
+ projectKey: verifiedToken[publicClaimForProjectKey]
87
+ };
88
+ const userPermissions = verifiedToken[publicClaimForUserPermissionsKey];
89
+ if (Boolean(userPermissions?.length)) {
90
+ request.session.userPermissions = userPermissions;
91
+ }
92
+ };
93
+
94
+ // Given a cloud identifier, try to map it to one of the supported
95
+ // environments and return the MC API URL for that environment.
96
+ // The URL points to the new hostnames.
97
+ // https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames
98
+ const mapCloudIdentifierToIssuer = issuer => {
99
+ switch (issuer) {
100
+ case CLOUD_IDENTIFIERS.GCP_AU:
101
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_AU];
102
+ case CLOUD_IDENTIFIERS.GCP_EU:
103
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_EU];
104
+ case CLOUD_IDENTIFIERS.GCP_US:
105
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_US];
106
+ case CLOUD_IDENTIFIERS.AWS_EU:
107
+ case CLOUD_IDENTIFIERS.AWS_FRA:
108
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_EU];
109
+ case CLOUD_IDENTIFIERS.AWS_US:
110
+ case CLOUD_IDENTIFIERS.AWS_OHIO:
111
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_US];
112
+ default:
113
+ return undefined;
114
+ }
115
+ };
116
+ // Given a cloud identifier, try to map it to a legacy hostname.
117
+ // This is for backwards compatibility.
118
+ const mapToLegacyIssuer = cloudIdentifier => {
119
+ switch (cloudIdentifier) {
120
+ case CLOUD_IDENTIFIERS.GCP_EU:
121
+ return 'https://mc-api.commercetools.com';
122
+ case CLOUD_IDENTIFIERS.GCP_US:
123
+ return 'https://mc-api.commercetools.co';
124
+ default:
125
+ return undefined;
126
+ }
127
+ };
128
+ // Verifies that the issuer is a valid URL.
129
+ const throwIfIssuerIsNotAValidUrl = issuer => {
130
+ try {
131
+ new _URL__default["default"](issuer);
132
+ } catch (error) {
133
+ throw new Error(`Invalid issuer URL "${issuer}". Expected a valid URL to the Merchant Center API Gateway, or a cloud identifier to one of the available cloud regions. See https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames.`);
134
+ }
135
+ };
136
+ // Validates required option values.
137
+ const validateRequiredValues = options => {
138
+ if (!options.audience) {
139
+ throw new Error(`Missing required option "audience"`);
140
+ }
141
+ if (!options.issuer) {
142
+ throw new Error(`Missing required option "issuer"`);
143
+ }
144
+ };
145
+ // Attempt to parse the given issuer. If the value is a cloud identifier, it will
146
+ // be mapped to one of the supported values. If not, we assume the value is a valid URL.
147
+ const getConfiguredDefaultIssuer = options => {
148
+ const issuer = mapCloudIdentifierToIssuer(options.issuer);
149
+ if (!issuer) {
150
+ throwIfIssuerIsNotAValidUrl(options.issuer);
151
+ return options.issuer;
152
+ }
153
+ return issuer;
154
+ };
155
+
156
+ // Construct the audience from the given option + the request path.
157
+ // If the request path is `/`, do not append it to the audience, otherwise
158
+ // the token validation might fail because of mismatching audiences.
159
+ const getConfiguredAudience = (options, requestPath) => {
160
+ // remove the trailing slash
161
+ const url = new _URL__default["default"](`${options.audience.replace(/\/?$/, '')}${requestPath}`);
162
+ switch (options.audiencePolicy) {
163
+ case 'forward-url-origin':
164
+ return url.origin;
165
+ default:
166
+ {
167
+ if (requestPath === '/') {
168
+ return url.origin;
169
+ }
170
+ return `${url.origin}${url.pathname}`;
171
+ }
172
+ }
173
+ };
174
+ const jwksClientByIssuer = new _Map__default["default"]();
175
+ function getJwksClientByIssuer(issuer) {
176
+ const client = jwksClientByIssuer.get(issuer);
177
+ if (client) {
178
+ return client;
179
+ }
180
+ const newClient = jose.createRemoteJWKSet(new _URL__default["default"](`/.well-known/jwks.json`, issuer));
181
+ jwksClientByIssuer.set(issuer, newClient);
182
+ return newClient;
183
+ }
184
+ function createSessionAuthVerifier(options) {
185
+ validateRequiredValues(options);
186
+ const configuredDefaultIssuer = getConfiguredDefaultIssuer(options);
187
+
188
+ // Returns an async HTTP handler.
189
+ return async request => {
190
+ // Get the cloud identifier header, forwarded by the `/proxy/forward-to` endpoint.
191
+ const cloudIdentifierHeader = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.CLOUD_IDENTIFIER, `Missing "X-MC-API-Cloud-Identifier" header.`);
192
+ let issuer = options.inferIssuer && cloudIdentifierHeader ? mapCloudIdentifierToIssuer(cloudIdentifierHeader) ?? configuredDefaultIssuer : configuredDefaultIssuer;
193
+
194
+ // Get the `Accept-version` header, forwarded by the `/proxy/forward-to` endpoint.
195
+ // The version should be sent by the client making the request, to use the features of v2.
196
+ const proxyForwardVersion = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.FORWARD_TO_VERSION, `Missing "X-MC-API-Forward-To-Version" header.`);
197
+ if (proxyForwardVersion === 'v1') {
198
+ // Fall back to legacy issuer domains
199
+ issuer = mapToLegacyIssuer(cloudIdentifierHeader) ?? issuer;
200
+ }
201
+ const requestUrlPath = options.getRequestUrl ? options.getRequestUrl(request) : request.originalUrl ?? request.url;
202
+ if (!requestUrlPath || !_startsWithInstanceProperty__default["default"](requestUrlPath).call(requestUrlPath, '/')) {
203
+ throw new Error(`Invalid request URI path "${requestUrlPath}". Please make sure that the "request" object has either a property "originalUrl" or "url". If not, you should implement the "getRequestUrl" function and make sure to return a valid URI path value starting with "/". More info at https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api#validating-the-json-web-token`);
204
+ }
205
+ const audience = getConfiguredAudience(options, requestUrlPath);
206
+ const authorizationHeader = request.headers['authorization'];
207
+ if (typeof authorizationHeader !== 'string') {
208
+ throw new Error(`Missing "authorization" header`);
209
+ }
210
+ const exchangeToken = authorizationHeader.replace(/^Bearer (.*)$/, '$1');
211
+ const jwksClient = getJwksClientByIssuer(issuer);
212
+ const verifiedToken = await jose.jwtVerify(exchangeToken, jwksClient, {
213
+ algorithms: ['RS256'],
214
+ audience,
215
+ issuer
216
+ });
217
+ writeSessionContext(request, verifiedToken.payload);
218
+ return;
219
+ };
220
+ }
221
+
222
+ function createSessionMiddleware(options) {
223
+ const sessionAuthVerifier = createSessionAuthVerifier(options);
224
+ return async (request, _response, next) => {
225
+ try {
226
+ await sessionAuthVerifier(request);
227
+ next();
228
+ } catch (error) {
229
+ next(error);
230
+ }
231
+ };
232
+ }
233
+
234
+ exports.CLOUD_IDENTIFIERS = CLOUD_IDENTIFIERS;
235
+ exports.MC_API_URLS = MC_API_URLS;
236
+ exports.createSessionAuthVerifier = createSessionAuthVerifier;
237
+ exports.createSessionMiddleware = createSessionMiddleware;
@@ -0,0 +1,221 @@
1
+ import _URL from '@babel/runtime-corejs3/core-js-stable/url';
2
+ import _Map from '@babel/runtime-corejs3/core-js-stable/map';
3
+ import _startsWithInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/starts-with';
4
+ import { jwtVerify, createRemoteJWKSet } from 'jose';
5
+ import _slicedToArray from '@babel/runtime-corejs3/helpers/esm/slicedToArray';
6
+ import _findInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/find';
7
+ import _Object$entries from '@babel/runtime-corejs3/core-js-stable/object/entries';
8
+ import _Array$isArray from '@babel/runtime-corejs3/core-js-stable/array/is-array';
9
+
10
+ const CLOUD_IDENTIFIERS = {
11
+ GCP_AU: 'gcp-au',
12
+ GCP_EU: 'gcp-eu',
13
+ GCP_US: 'gcp-us',
14
+ AWS_EU: 'aws-eu',
15
+ AWS_US: 'aws-us',
16
+ /**
17
+ * @deprecated: use `AWS_EU` instead
18
+ */
19
+ AWS_FRA: 'aws-fra',
20
+ /**
21
+ * @deprecated: use `AWS_US` instead
22
+ */
23
+ AWS_OHIO: 'aws-ohio'
24
+ };
25
+ const MC_API_URLS = {
26
+ [CLOUD_IDENTIFIERS.GCP_AU]: 'https://mc-api.australia-southeast1.gcp.commercetools.com',
27
+ [CLOUD_IDENTIFIERS.GCP_EU]: 'https://mc-api.europe-west1.gcp.commercetools.com',
28
+ [CLOUD_IDENTIFIERS.GCP_US]: 'https://mc-api.us-central1.gcp.commercetools.com',
29
+ [CLOUD_IDENTIFIERS.AWS_EU]: 'https://mc-api.eu-central-1.aws.commercetools.com',
30
+ [CLOUD_IDENTIFIERS.AWS_US]: 'https://mc-api.us-east-2.aws.commercetools.com',
31
+ /**
32
+ * @deprecated: use `AWS_EU` instead
33
+ */
34
+ [CLOUD_IDENTIFIERS.AWS_FRA]: 'https://mc-api.eu-central-1.aws.commercetools.com',
35
+ /**
36
+ * @deprecated: use `AWS_US` instead
37
+ */
38
+ [CLOUD_IDENTIFIERS.AWS_OHIO]: 'https://mc-api.us-east-2.aws.commercetools.com'
39
+ };
40
+ const MC_API_PROXY_HEADERS = {
41
+ FORWARD_TO_VERSION: 'x-mc-api-forward-to-version',
42
+ CLOUD_IDENTIFIER: 'x-mc-api-cloud-identifier'
43
+ };
44
+
45
+ const getHeaderByCaseInsensitiveKey = (headers, headerKey) => {
46
+ var _context;
47
+ const matchingHeader = _findInstanceProperty(_context = _Object$entries(headers)).call(_context, _ref => {
48
+ let _ref2 = _slicedToArray(_ref, 1),
49
+ key = _ref2[0];
50
+ return headerKey.toLowerCase() === key.toLowerCase();
51
+ });
52
+ if (matchingHeader && matchingHeader.length > 0) {
53
+ const _matchingHeader = _slicedToArray(matchingHeader, 2),
54
+ headerValue = _matchingHeader[1];
55
+ return _Array$isArray(headerValue) ? headerValue[0] : headerValue;
56
+ }
57
+ return undefined;
58
+ };
59
+ const getFirstHeaderValueOrThrow = (headers, headerKey, errorMessage) => {
60
+ const headerValue = getHeaderByCaseInsensitiveKey(headers, headerKey);
61
+ if (!headerValue) {
62
+ throw new Error(errorMessage);
63
+ }
64
+ return headerValue;
65
+ };
66
+
67
+ // Assign a session object to the request object.
68
+ const writeSessionContext = (request, verifiedToken) => {
69
+ const publicClaimForProjectKey = `${verifiedToken.iss}/claims/project_key`;
70
+ const publicClaimForUserPermissionsKey = `${verifiedToken.iss}/claims/user_permissions`;
71
+ request.session = {
72
+ userId: verifiedToken.sub,
73
+ projectKey: verifiedToken[publicClaimForProjectKey]
74
+ };
75
+ const userPermissions = verifiedToken[publicClaimForUserPermissionsKey];
76
+ if (Boolean(userPermissions?.length)) {
77
+ request.session.userPermissions = userPermissions;
78
+ }
79
+ };
80
+
81
+ // Given a cloud identifier, try to map it to one of the supported
82
+ // environments and return the MC API URL for that environment.
83
+ // The URL points to the new hostnames.
84
+ // https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames
85
+ const mapCloudIdentifierToIssuer = issuer => {
86
+ switch (issuer) {
87
+ case CLOUD_IDENTIFIERS.GCP_AU:
88
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_AU];
89
+ case CLOUD_IDENTIFIERS.GCP_EU:
90
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_EU];
91
+ case CLOUD_IDENTIFIERS.GCP_US:
92
+ return MC_API_URLS[CLOUD_IDENTIFIERS.GCP_US];
93
+ case CLOUD_IDENTIFIERS.AWS_EU:
94
+ case CLOUD_IDENTIFIERS.AWS_FRA:
95
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_EU];
96
+ case CLOUD_IDENTIFIERS.AWS_US:
97
+ case CLOUD_IDENTIFIERS.AWS_OHIO:
98
+ return MC_API_URLS[CLOUD_IDENTIFIERS.AWS_US];
99
+ default:
100
+ return undefined;
101
+ }
102
+ };
103
+ // Given a cloud identifier, try to map it to a legacy hostname.
104
+ // This is for backwards compatibility.
105
+ const mapToLegacyIssuer = cloudIdentifier => {
106
+ switch (cloudIdentifier) {
107
+ case CLOUD_IDENTIFIERS.GCP_EU:
108
+ return 'https://mc-api.commercetools.com';
109
+ case CLOUD_IDENTIFIERS.GCP_US:
110
+ return 'https://mc-api.commercetools.co';
111
+ default:
112
+ return undefined;
113
+ }
114
+ };
115
+ // Verifies that the issuer is a valid URL.
116
+ const throwIfIssuerIsNotAValidUrl = issuer => {
117
+ try {
118
+ new _URL(issuer);
119
+ } catch (error) {
120
+ throw new Error(`Invalid issuer URL "${issuer}". Expected a valid URL to the Merchant Center API Gateway, or a cloud identifier to one of the available cloud regions. See https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api#hostnames.`);
121
+ }
122
+ };
123
+ // Validates required option values.
124
+ const validateRequiredValues = options => {
125
+ if (!options.audience) {
126
+ throw new Error(`Missing required option "audience"`);
127
+ }
128
+ if (!options.issuer) {
129
+ throw new Error(`Missing required option "issuer"`);
130
+ }
131
+ };
132
+ // Attempt to parse the given issuer. If the value is a cloud identifier, it will
133
+ // be mapped to one of the supported values. If not, we assume the value is a valid URL.
134
+ const getConfiguredDefaultIssuer = options => {
135
+ const issuer = mapCloudIdentifierToIssuer(options.issuer);
136
+ if (!issuer) {
137
+ throwIfIssuerIsNotAValidUrl(options.issuer);
138
+ return options.issuer;
139
+ }
140
+ return issuer;
141
+ };
142
+
143
+ // Construct the audience from the given option + the request path.
144
+ // If the request path is `/`, do not append it to the audience, otherwise
145
+ // the token validation might fail because of mismatching audiences.
146
+ const getConfiguredAudience = (options, requestPath) => {
147
+ // remove the trailing slash
148
+ const url = new _URL(`${options.audience.replace(/\/?$/, '')}${requestPath}`);
149
+ switch (options.audiencePolicy) {
150
+ case 'forward-url-origin':
151
+ return url.origin;
152
+ default:
153
+ {
154
+ if (requestPath === '/') {
155
+ return url.origin;
156
+ }
157
+ return `${url.origin}${url.pathname}`;
158
+ }
159
+ }
160
+ };
161
+ const jwksClientByIssuer = new _Map();
162
+ function getJwksClientByIssuer(issuer) {
163
+ const client = jwksClientByIssuer.get(issuer);
164
+ if (client) {
165
+ return client;
166
+ }
167
+ const newClient = createRemoteJWKSet(new _URL(`/.well-known/jwks.json`, issuer));
168
+ jwksClientByIssuer.set(issuer, newClient);
169
+ return newClient;
170
+ }
171
+ function createSessionAuthVerifier(options) {
172
+ validateRequiredValues(options);
173
+ const configuredDefaultIssuer = getConfiguredDefaultIssuer(options);
174
+
175
+ // Returns an async HTTP handler.
176
+ return async request => {
177
+ // Get the cloud identifier header, forwarded by the `/proxy/forward-to` endpoint.
178
+ const cloudIdentifierHeader = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.CLOUD_IDENTIFIER, `Missing "X-MC-API-Cloud-Identifier" header.`);
179
+ let issuer = options.inferIssuer && cloudIdentifierHeader ? mapCloudIdentifierToIssuer(cloudIdentifierHeader) ?? configuredDefaultIssuer : configuredDefaultIssuer;
180
+
181
+ // Get the `Accept-version` header, forwarded by the `/proxy/forward-to` endpoint.
182
+ // The version should be sent by the client making the request, to use the features of v2.
183
+ const proxyForwardVersion = getFirstHeaderValueOrThrow(request.headers, MC_API_PROXY_HEADERS.FORWARD_TO_VERSION, `Missing "X-MC-API-Forward-To-Version" header.`);
184
+ if (proxyForwardVersion === 'v1') {
185
+ // Fall back to legacy issuer domains
186
+ issuer = mapToLegacyIssuer(cloudIdentifierHeader) ?? issuer;
187
+ }
188
+ const requestUrlPath = options.getRequestUrl ? options.getRequestUrl(request) : request.originalUrl ?? request.url;
189
+ if (!requestUrlPath || !_startsWithInstanceProperty(requestUrlPath).call(requestUrlPath, '/')) {
190
+ throw new Error(`Invalid request URI path "${requestUrlPath}". Please make sure that the "request" object has either a property "originalUrl" or "url". If not, you should implement the "getRequestUrl" function and make sure to return a valid URI path value starting with "/". More info at https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api#validating-the-json-web-token`);
191
+ }
192
+ const audience = getConfiguredAudience(options, requestUrlPath);
193
+ const authorizationHeader = request.headers['authorization'];
194
+ if (typeof authorizationHeader !== 'string') {
195
+ throw new Error(`Missing "authorization" header`);
196
+ }
197
+ const exchangeToken = authorizationHeader.replace(/^Bearer (.*)$/, '$1');
198
+ const jwksClient = getJwksClientByIssuer(issuer);
199
+ const verifiedToken = await jwtVerify(exchangeToken, jwksClient, {
200
+ algorithms: ['RS256'],
201
+ audience,
202
+ issuer
203
+ });
204
+ writeSessionContext(request, verifiedToken.payload);
205
+ return;
206
+ };
207
+ }
208
+
209
+ function createSessionMiddleware(options) {
210
+ const sessionAuthVerifier = createSessionAuthVerifier(options);
211
+ return async (request, _response, next) => {
212
+ try {
213
+ await sessionAuthVerifier(request);
214
+ next();
215
+ } catch (error) {
216
+ next(error);
217
+ }
218
+ };
219
+ }
220
+
221
+ export { CLOUD_IDENTIFIERS, MC_API_URLS, createSessionAuthVerifier, createSessionMiddleware };
@@ -0,0 +1,12 @@
1
+ import type { TSessionMiddlewareOptions, TSession, TBaseRequest } from "./types.js";
2
+ export type TDecodedJWT = {
3
+ sub: string;
4
+ iss: string;
5
+ [property: string]: string | string[];
6
+ };
7
+ declare const writeSessionContext: <Request extends TBaseRequest>(request: Request & {
8
+ session?: TSession;
9
+ }, verifiedToken: TDecodedJWT) => void;
10
+ export declare const getConfiguredAudience: <Request extends TBaseRequest>(options: TSessionMiddlewareOptions<Request>, requestPath: string) => string;
11
+ declare function createSessionAuthVerifier<Request extends TBaseRequest>(options: TSessionMiddlewareOptions<Request>): (request: Request) => Promise<void>;
12
+ export { createSessionAuthVerifier, writeSessionContext };
@@ -0,0 +1,34 @@
1
+ export declare const CLOUD_IDENTIFIERS: {
2
+ readonly GCP_AU: "gcp-au";
3
+ readonly GCP_EU: "gcp-eu";
4
+ readonly GCP_US: "gcp-us";
5
+ readonly AWS_EU: "aws-eu";
6
+ readonly AWS_US: "aws-us";
7
+ /**
8
+ * @deprecated: use `AWS_EU` instead
9
+ */
10
+ readonly AWS_FRA: "aws-fra";
11
+ /**
12
+ * @deprecated: use `AWS_US` instead
13
+ */
14
+ readonly AWS_OHIO: "aws-ohio";
15
+ };
16
+ export declare const MC_API_URLS: {
17
+ readonly "gcp-au": "https://mc-api.australia-southeast1.gcp.commercetools.com";
18
+ readonly "gcp-eu": "https://mc-api.europe-west1.gcp.commercetools.com";
19
+ readonly "gcp-us": "https://mc-api.us-central1.gcp.commercetools.com";
20
+ readonly "aws-eu": "https://mc-api.eu-central-1.aws.commercetools.com";
21
+ readonly "aws-us": "https://mc-api.us-east-2.aws.commercetools.com";
22
+ /**
23
+ * @deprecated: use `AWS_EU` instead
24
+ */
25
+ readonly "aws-fra": "https://mc-api.eu-central-1.aws.commercetools.com";
26
+ /**
27
+ * @deprecated: use `AWS_US` instead
28
+ */
29
+ readonly "aws-ohio": "https://mc-api.us-east-2.aws.commercetools.com";
30
+ };
31
+ export declare const MC_API_PROXY_HEADERS: {
32
+ readonly FORWARD_TO_VERSION: "x-mc-api-forward-to-version";
33
+ readonly CLOUD_IDENTIFIER: "x-mc-api-cloud-identifier";
34
+ };
@@ -0,0 +1,3 @@
1
+ export { CLOUD_IDENTIFIERS, MC_API_URLS } from "./constants.js";
2
+ export { createSessionAuthVerifier } from "./auth.js";
3
+ export { default as createSessionMiddleware } from "./middlewares/session-middleware.js";
@@ -0,0 +1,4 @@
1
+ import type { Response, NextFunction } from 'express';
2
+ import type { TBaseRequest, TSessionMiddlewareOptions } from "../types.js";
3
+ declare function createSessionMiddleware<Request extends TBaseRequest>(options: TSessionMiddlewareOptions<Request>): (request: Request, _response: Response, next: NextFunction) => Promise<void>;
4
+ export default createSessionMiddleware;
@@ -0,0 +1,53 @@
1
+ import { CLOUD_IDENTIFIERS } from "./constants.js";
2
+ export type TAudience = string;
3
+ export type TIssuer = string;
4
+ export type TCloudIdentifier = (typeof CLOUD_IDENTIFIERS)[keyof typeof CLOUD_IDENTIFIERS];
5
+ export type TAudiencePolicy = 'forward-url-full-path' | 'forward-url-origin';
6
+ export interface TBaseRequest {
7
+ headers: Record<string, string | string[] | undefined>;
8
+ url?: string;
9
+ originalUrl?: string;
10
+ }
11
+ export type TSessionMiddlewareOptions<Request extends TBaseRequest> = {
12
+ /**
13
+ * The public-facing URL used to connect to the server / serverless function.
14
+ * The value should only contain the origin URL (protocol, hostname, port),
15
+ * the request path is inferred from the incoming request.
16
+ */
17
+ audience: TAudience;
18
+ /**
19
+ * How the audience value should be exchanged between the server and the client.
20
+ * By default it uses the full URL (origin + pathname).
21
+ */
22
+ audiencePolicy?: TAudiencePolicy;
23
+ /**
24
+ * The cloud identifier (see `CLOUD_IDENTIFIERS`) that maps to the MC API URL
25
+ * of the related cloud region or the MC API URL.
26
+ */
27
+ issuer: TCloudIdentifier | TIssuer;
28
+ /**
29
+ * Determines whether the issuer should be inferred from the custom request
30
+ * HTTP header `x-mc-api-cloud-identifier` which is sent by the MC API when
31
+ * forwarding the request.
32
+ * This might be useful in case the server is used in multiple regions.
33
+ */
34
+ inferIssuer?: boolean;
35
+ /**
36
+ * Options for the `jwksRsa.expressJwtSecret`
37
+ *
38
+ * @deprecated
39
+ */
40
+ jwks?: unknown;
41
+ /**
42
+ * By default we assume that the `request` is a Node.js-like object having either
43
+ * an `originalUrl` or `url` properties.
44
+ * If that's not the case (for example in AWS Lambda functions) you need to correctly
45
+ * map the URL (URI path + query string) of the `request`.
46
+ */
47
+ getRequestUrl?: (request: Request) => string;
48
+ };
49
+ export type TSession = {
50
+ userId: string;
51
+ projectKey: string;
52
+ userPermissions?: string[];
53
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@commercetools-backend/express",
3
+ "version": "0.0.0-CRAFT-1791-20251006162610",
4
+ "description": "Zero-config HTTP server as Express.js to facilitate development",
5
+ "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/commercetools/merchant-center-application-kit.git",
9
+ "directory": "packages-backend/express"
10
+ },
11
+ "homepage": "https://docs.commercetools.com/merchant-center-customizations",
12
+ "keywords": [
13
+ "javascript",
14
+ "nodejs",
15
+ "express",
16
+ "http",
17
+ "server",
18
+ "toolkit"
19
+ ],
20
+ "license": "MIT",
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "main": "dist/commercetools-backend-express.cjs.js",
25
+ "module": "dist/commercetools-backend-express.esm.js",
26
+ "files": [
27
+ "dist",
28
+ "package.json",
29
+ "LICENSE",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@babel/runtime": "^7.22.15",
34
+ "@babel/runtime-corejs3": "^7.22.15",
35
+ "@types/express": "^4.17.17",
36
+ "@types/node": "^22.13.2",
37
+ "express": "^4.21.2",
38
+ "jose": "^5.10.0"
39
+ },
40
+ "devDependencies": {
41
+ "@tsconfig/node22": "^22.0.0",
42
+ "msw": "^1.3.5",
43
+ "typescript": "^5.2.2"
44
+ },
45
+ "engines": {
46
+ "node": "18.x || 20.x || >=22.0.0"
47
+ }
48
+ }