diff --git a/packages/template-retail-react-app/app/commerce-api/auth.js b/packages/template-retail-react-app/app/commerce-api/auth.js index 0e8a8a873a..37e5b66fcf 100644 --- a/packages/template-retail-react-app/app/commerce-api/auth.js +++ b/packages/template-retail-react-app/app/commerce-api/auth.js @@ -305,7 +305,9 @@ class Auth { const authorization = `Basic ${btoa(`${credentials.email}:${credentials.password}`)}` const options = { headers: { - Authorization: authorization, + // @@@ + // Authorization: authorization, + 'X-Authorization': authorization, 'Content-Type': `application/x-www-form-urlencoded` }, body: { @@ -352,7 +354,7 @@ class Auth { const options = { headers: { - Authorization: '', + // Authorization: '', 'Content-Type': `application/x-www-form-urlencoded` }, parameters: { diff --git a/packages/template-retail-react-app/app/commerce-api/index.js b/packages/template-retail-react-app/app/commerce-api/index.js index 325ce70f72..d8ba7053d4 100644 --- a/packages/template-retail-react-app/app/commerce-api/index.js +++ b/packages/template-retail-react-app/app/commerce-api/index.js @@ -205,7 +205,9 @@ class CommerceAPI { const [fetchOptions, ...restParams] = params const newFetchOptions = { ...fetchOptions, - headers: {...fetchOptions.headers, Authorization: this.auth.authToken} + // @@@ + // headers: {...fetchOptions.headers, Authorization: this.auth.authToken} + headers: {...fetchOptions.headers, 'X-Authorization': this.auth.authToken} } return [newFetchOptions, ...restParams] } diff --git a/packages/template-retail-react-app/app/commerce-api/utils.js b/packages/template-retail-react-app/app/commerce-api/utils.js index f653b51a8b..ddedba2557 100644 --- a/packages/template-retail-react-app/app/commerce-api/utils.js +++ b/packages/template-retail-react-app/app/commerce-api/utils.js @@ -184,7 +184,9 @@ export const createOcapiFetch = (commerceAPIConfig) => async ( methodName, body ) => { - const proxy = `/mobify/proxy/ocapi` + // @@@ + // const proxy = `/mobify/proxy/ocapi` + const proxy = `/proxy/ocapi` // The api config will only have `ocapiHost` during testing to workaround localhost proxy const host = commerceAPIConfig.ocapiHost diff --git a/packages/template-retail-react-app/app/ssr.js b/packages/template-retail-react-app/app/ssr.js index 261164f610..f1121d6ce7 100644 --- a/packages/template-retail-react-app/app/ssr.js +++ b/packages/template-retail-react-app/app/ssr.js @@ -11,6 +11,7 @@ const {getRuntime} = require('pwa-kit-runtime/ssr/server/express') const {isRemote} = require('pwa-kit-runtime/utils/ssr-server') const {getConfig} = require('pwa-kit-runtime/utils/ssr-config') const helmet = require('helmet') +const {createProxyMiddleware} = require('http-proxy-middleware') const options = { // The build directory (an absolute path) @@ -32,7 +33,86 @@ const options = { const runtime = getRuntime() +// @@@ +const AUTH = { + username: 'storefront', + password: 'password' +} + +function basicAuthMiddleware(req, res, next) { + const shouldSkipAuth = + req.path.startsWith('/proxy') || + req.path.startsWith('/mobify') || + req.path.startsWith('/callback') + if (shouldSkipAuth) { + return next() + } + + const authorization = (req.get('authorization') || '').split(' ')[1] || '' + const [username, password] = Buffer.from(authorization, 'base64') + .toString() + .split(':') + + const hasValidAuth = username == AUTH.username && password == AUTH.password + if (hasValidAuth) { + return next() + } + + res.set('WWW-Authenticate', 'Basic realm="Storefront"') + res.status(401).send('Auth Required! :woman-gesturing-no:') +} + +// Basic auth credentials are in the `authorization` header, while +// SCAPI/OCAPI JWTs are in `x-authorization`. +function swapAuthHeader(proxyReq, req, res) { + proxyReq.removeHeader('Authorization') + + const authorization = proxyReq.getHeader('X-Authorization') + proxyReq.removeHeader('X-Authorization') + // Avoid OCAPI origin protection. + proxyReq.removeHeader('Origin') + if (authorization) { + proxyReq.setHeader('Authorization', authorization) + } +} + +// Useful for testing! +const proxyHTTPBin = createProxyMiddleware({ + target: 'https://httpbin.org/anything', + secure: true, + changeOrigin: true, + onProxyReq: swapAuthHeader, + pathRewrite: { + '.*': '' + } +}) + +const proxySCAPI = createProxyMiddleware({ + target: 'https://kv7kzm78.api.commercecloud.salesforce.com', + secure: true, + changeOrigin: true, + onProxyReq: swapAuthHeader, + pathRewrite: { + '^/proxy/scapi/': '/' + } +}) + +const proxyOCAPI = createProxyMiddleware({ + target: 'https://zzte-053.sandbox.us02.dx.commercecloud.salesforce.com', + secure: true, + changeOrigin: true, + onProxyReq: swapAuthHeader, + pathRewrite: { + '^/proxy/ocapi/': '/' + } +}) + const {handler} = runtime.createHandler(options, (app) => { + app.use(basicAuthMiddleware) + app.all('/proxy/httpbin/*', proxyHTTPBin) + app.all('/proxy/scapi/*', proxySCAPI) + app.all('/proxy/ocapi/*', proxyOCAPI) + // Set HTTP security headers app.use( helmet({ diff --git a/packages/template-retail-react-app/config/default.js b/packages/template-retail-react-app/config/default.js index dfaf37f975..1431666d15 100644 --- a/packages/template-retail-react-app/config/default.js +++ b/packages/template-retail-react-app/config/default.js @@ -20,7 +20,9 @@ module.exports = { }, sites, commerceAPI: { - proxyPath: `/mobify/proxy/api`, + // @@@ + // proxyPath: `/mobify/proxy/api`, + proxyPath: `/proxy/api`, parameters: { clientId: 'c9c45bfd-0ed3-4aa2-9971-40f88962b836', organizationId: 'f_ecom_zzrf_001', diff --git a/packages/template-retail-react-app/package-lock.json b/packages/template-retail-react-app/package-lock.json index 7ac8d5ce2f..719e167049 100644 --- a/packages/template-retail-react-app/package-lock.json +++ b/packages/template-retail-react-app/package-lock.json @@ -1681,6 +1681,14 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "requires": { + "@types/node": "*" + } + }, "@types/inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", @@ -1755,8 +1763,7 @@ "@types/node": { "version": "14.18.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", - "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==", - "dev": true + "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==" }, "@types/parse-json": { "version": "4.0.0", @@ -2344,7 +2351,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -3312,6 +3318,11 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3508,7 +3519,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -3582,6 +3592,11 @@ "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3986,6 +4001,28 @@ "integrity": "sha1-oitBoMmx4tj6wb8baXxr1TLV9eQ=", "dev": true }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -4360,8 +4397,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -4373,7 +4409,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -4403,8 +4438,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -4427,6 +4461,11 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -5276,7 +5315,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -5813,8 +5851,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "3.0.0", @@ -6408,6 +6445,11 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -6937,7 +6979,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } diff --git a/packages/template-retail-react-app/package.json b/packages/template-retail-react-app/package.json index e90defbe63..8f9aab69d8 100644 --- a/packages/template-retail-react-app/package.json +++ b/packages/template-retail-react-app/package.json @@ -90,5 +90,8 @@ "iOS >= 9.0", "Android >= 4.4.4", "last 4 ChromeAndroid versions" - ] + ], + "dependencies": { + "http-proxy-middleware": "^2.0.6" + } }