Skip to content

Commit

Permalink
chore: update validation for web-sdk bridge URL (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
bywood committed May 20, 2024
1 parent 1700f9b commit 9c57a81
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 30 deletions.
40 changes: 33 additions & 7 deletions server/meta.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type SDKMeta = {|
|};

const emailRegex = /^.+@.+$/;
const semverRegex = /^[0-9.A-Za-z-+]+$/;

function validatePaymentsSDKUrl({ pathname, query, hash }) {
if (pathname !== SDK_PATH) {
Expand Down Expand Up @@ -83,12 +84,40 @@ function validateLegacySDKUrl({ pathname }) {
}
}

function validateWebSDKUrl({ pathname }) {
function validateWebSDKUrl({ pathname, query }) {
if (pathname !== WEB_SDK_BRIDGE_PATH) {
throw new Error(
`Invalid path for web-sdk bridge url: ${pathname || "undefined"}`
);
}
// check for extraneous parameters
Object.keys(query).forEach((param) => {
if (param !== "version" && param !== "origin") {
throw new Error(`Invalid parameter on web-sdk bridge url: ${param}`);
}
});
// validate the version parameter
if (query.version === undefined || !semverRegex.test(query.version)) {
throw new Error(
`Invalid version parameter on web-sdk bridge url: ${query.version}`
);
}
// validate the origin parameter
let url = null;
try {
// eslint-disable-next-line compat/compat
url = new URL(query.origin);
} catch (error) {
throw new Error(
`Invalid origin parameter on web-sdk bridge url: ${query.origin}`
);
}
// check that the origin URL only includes the origin
if (query.origin !== `${url.protocol}//${url.host}`) {
throw new Error(
`Invalid origin parameter on web-sdk bridge url: ${query.origin}`
);
}
}

function isLegacySDKUrl(hostname: string, pathname: string): boolean {
Expand Down Expand Up @@ -186,7 +215,7 @@ function validateSDKUrl(sdkUrl: string) {
if (isLegacySDKUrl(hostname, pathname)) {
validateLegacySDKUrl({ pathname });
} else if (isWebSDKUrl(hostname, pathname)) {
validateWebSDKUrl({ hostname, pathname });
validateWebSDKUrl({ pathname, query });
} else if (isSDKUrl(hostname)) {
if (hostname !== HOST.LOCALHOST && protocol !== PROTOCOL.HTTPS) {
throw new Error(
Expand Down Expand Up @@ -267,11 +296,8 @@ function sanitizeSDKUrl(sdkUrl: string): string {
// eslint-disable-next-line compat/compat
const url = new URL(sdkUrl);

// remove query string params for checkout.js and web-sdk
if (
isLegacySDKUrl(url.hostname, url.pathname) ||
isWebSDKUrl(url.hostname, url.pathname)
) {
// remove query string params for checkout.js
if (isLegacySDKUrl(url.hostname, url.pathname)) {
url.search = "";
url.hash = "";

Expand Down
176 changes: 153 additions & 23 deletions server/meta.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1223,49 +1223,179 @@ test("should error when invalid characters are found in the subdomain - we allow
});

test("should construct a valid web-sdk bridge url", () => {
const sdkUrl = "https://www.paypal.com/web-sdk/v6/bridge";
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?version=1.2.3&origin=https%3A%2F%2Fwww.example.com%3A8000";
const sdkUID = "abc123";

const { getSDKLoader } = unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": sdkUID,
},
})
).toString("base64")
);

const $ = cheerio.load(getSDKLoader());
const src = $("script").attr("src");
const script = $("script");
const src = script.attr("src");
const uid = script.attr("data-uid");

if (src !== sdkUrl) {
throw new Error(`Expected script url to be ${sdkUrl} - got ${src}`);
}
if (uid !== sdkUID) {
throw new Error(`Expected data UID be ${sdkUID} - got ${uid}`);
}
});

test("should prevent query string parameters and hashs on the web-sdk bridge url", () => {
test("should error when extra parameters are present", () => {
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?name=value#hashvalue";
"https://www.paypal.com/web-sdk/v6/bridge?version=1.2.3&origin=https%3A%2F%2Fwww.example.com%3A8000&name=value";

const { getSDKLoader } = unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
})
).toString("base64")
);
let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

const $ = cheerio.load(getSDKLoader());
const script = $("script");
const src = script.attr("src");
if (!error) {
throw new Error("Expected error to be thrown");
}
});

// eslint-disable-next-line compat/compat
const urlObject = new URL(sdkUrl);
// we expect the query string params to be stripped out
urlObject.search = "";
// we expect the hash to be stripped out
urlObject.hash = "";
const expectedUrl = urlObject.toString();
test("should error when the version parameter is missing", () => {
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?origin=https%3A%2F%2Fwww.example.com%3A8000";

if (src !== expectedUrl) {
throw new Error(`Expected script url to be ${expectedUrl} - got ${src}`);
let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

if (!error) {
throw new Error("Expected error to be thrown");
}
});

test("should error when the version parameter is invalid", () => {
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?version=^1.2.3&origin=https%3A%2F%2Fwww.example.com%3A8000";

let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

if (!error) {
throw new Error("Expected error to be thrown");
}
});

test("should error when the origin parameter is missing", () => {
const sdkUrl = "https://www.paypal.com/web-sdk/v6/bridge?version=1.2.3";

let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

if (!error) {
throw new Error("Expected error to be thrown");
}
});

test("should error when the origin parameter is invalid", () => {
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?version=1.2.3&origin=example";

let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

if (!error) {
throw new Error("Expected error to be thrown");
}
});

test("should error when the origin parameter is not just the origin", () => {
const sdkUrl =
"https://www.paypal.com/web-sdk/v6/bridge?version=1.2.3&origin=https%3A%2F%2Fwww.example.com%3A8000%2Fpath";

let error = null;
try {
unpackSDKMeta(
Buffer.from(
JSON.stringify({
url: sdkUrl,
attrs: {
"data-uid": "abc123",
},
})
).toString("base64")
);
} catch (err) {
error = err;
}

if (!error) {
throw new Error("Expected error to be thrown");
}
});

0 comments on commit 9c57a81

Please sign in to comment.