Skip to content

Commit

Permalink
Add initial entry point for vault flows with SDK token (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
elizabethmv authored Apr 17, 2024
1 parent 19fd64e commit e01e295
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"flow-typed": "^3.8.0",
"husky": "^8.0.1",
"jsdom": "^20.0.3",
"jsonwebtoken": "^9.0.2",
"lint-staged": "^13.0.3",
"prettier": "2.8.8",
"@vitest/coverage-v8": "^1.0.0",
Expand Down
41 changes: 37 additions & 4 deletions src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,6 @@ export function getAmount(): ?string {
return amount;
}

export function getUserIDToken(): ?string {
return getSDKAttribute(SDK_SETTINGS.USER_ID_TOKEN);
}

export function getClientAccessToken(): ?string {
const clientToken = getClientToken();

Expand Down Expand Up @@ -330,10 +326,47 @@ export function getUserExperienceFlow(): ?string {
return getSDKAttribute(SDK_SETTINGS.USER_EXPERIENCE_FLOW);
}

export function getUserIDToken(): ?string {
if (
getSDKAttribute(SDK_SETTINGS.SDK_TOKEN) &&
!getSDKAttribute(SDK_SETTINGS.USER_ID_TOKEN)
) {
return getSDKAttribute(SDK_SETTINGS.SDK_TOKEN);
}

return getSDKAttribute(SDK_SETTINGS.USER_ID_TOKEN);
}

export function getSDKToken(): ?string {
if (
getSDKAttribute(SDK_SETTINGS.SDK_TOKEN) &&
getSDKAttribute(SDK_SETTINGS.USER_ID_TOKEN)
) {
throw new Error("Do not pass SDK token and ID token");
}

return getSDKAttribute(SDK_SETTINGS.SDK_TOKEN);
}

type decodedCustomerId = (string) => string;
export const decodeCustomerIdFromToken: decodedCustomerId = memoize((token) => {
try {
if (token && typeof atob === "function") {
const { options = {} } = JSON.parse(window.atob(token.split(".")[1]));
return options.customer_id || "";
}

return "";
} catch {
throw new Error("Error decoding SDK token");
}
});

export function getCustomerId(): string {
const sdkToken = getSDKAttribute(SDK_SETTINGS.SDK_TOKEN) || "";
return decodeCustomerIdFromToken(sdkToken);
}

/* v8 ignore next 3 */
export function isChildWindow(): boolean {
return Boolean(window.xprops);
Expand Down
88 changes: 87 additions & 1 deletion src/script.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @flow */
/* eslint max-lines: off */
import { describe, it, afterEach, beforeEach, expect, vi } from "vitest";
import jwt from "jsonwebtoken";
import { base64encode, getCurrentScript, memoize } from "@krakenjs/belter/src";
import { SDK_SETTINGS } from "@paypal/sdk-constants/src";

Expand Down Expand Up @@ -28,6 +29,8 @@ import {
getBuyerCountry,
getAmount,
getUserIDToken,
getSDKToken,
getCustomerId,
getCSPNonce,
getEnableThreeDomainSecure,
getUserExperienceFlow,
Expand Down Expand Up @@ -535,7 +538,7 @@ describe(`script cases`, () => {
});
});

it("getUserIDToken return a token string", () => {
it("getUserIDToken returns a token string", () => {
const inputToken = "some-token";
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-user-id-token", inputToken);
Expand All @@ -546,6 +549,89 @@ describe(`script cases`, () => {
expect(result).toEqual(inputToken);
});

it("getUserIDToken is set as SDK token if SDK token is passed only", () => {
const sdkToken = "some-token";
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", sdkToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

const result = getUserIDToken();
expect(result).toEqual(sdkToken);
});

it("getSDKToken returns a token string", () => {
const inputToken = "some-token";
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", inputToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

const result = getSDKToken();
expect(result).toEqual(inputToken);
});

it("getSDKToken errors if ID token is also passed", () => {
const inputToken = "some-token";
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", inputToken);
mockElement.setAttribute("data-user-id-token", inputToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

expect(getSDKToken).toThrow("Do not pass SDK token and ID token");
});

it("getCustomerId returns a string of the decoded customer_id from the SDK token", () => {
const encodedCustomerId = "test123";
const mockToken = jwt.sign(
{
options: {
customer_id: encodedCustomerId,
},
},
"test"
);
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", mockToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

const result = getCustomerId();
expect(result).toEqual(encodedCustomerId);
});

it("getCustomerId returns an empty string there is no encoded customer ID", () => {
const mockToken = jwt.sign(
{
options: {},
},
"test"
);
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", mockToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

const result = getCustomerId();
expect(result).toEqual("");
});

it("getCustomerId returns an empty string there is no token passed", () => {
const result = getCustomerId();
expect(result).toEqual("");
});

it("getCustomerId throws an error if there is a bad token passed", () => {
const inputToken = "-123";
const mockElement = makeMockScriptElement(mockScriptSrc);
mockElement.setAttribute("data-sdk-client-token", inputToken);
// $FlowIgnore
getCurrentScript.mockReturnValue(mockElement);

expect(getCustomerId).toThrow("Error decoding SDK token");
});

it("getCSPNonce should return a data-csp-nonce string", () => {
const inputCspNonce = "some-csp-nonce";
const mockElement = makeMockScriptElement(mockScriptSrc);
Expand Down

0 comments on commit e01e295

Please sign in to comment.