Use b64url and fix incoming data validation (#156)
This commit is contained in:
parent
74e3f40ca5
commit
b84049246b
3 changed files with 144 additions and 5 deletions
|
@ -5,6 +5,10 @@ import { hasProp } from "../has-prop";
|
||||||
*/
|
*/
|
||||||
export const hasAuthData = (data: unknown) =>
|
export const hasAuthData = (data: unknown) =>
|
||||||
hasProp(data, "domain") &&
|
hasProp(data, "domain") &&
|
||||||
|
data.domain &&
|
||||||
hasProp(data, "token") &&
|
hasProp(data, "token") &&
|
||||||
|
data.token &&
|
||||||
hasProp(data, "appId") &&
|
hasProp(data, "appId") &&
|
||||||
hasProp(data, "saleorApiUrl");
|
data.appId &&
|
||||||
|
hasProp(data, "saleorApiUrl") &&
|
||||||
|
data.saleorApiUrl;
|
||||||
|
|
120
src/APL/saleor-cloud-apl.test.ts
Normal file
120
src/APL/saleor-cloud-apl.test.ts
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { AuthData } from "./apl";
|
||||||
|
import { SaleorCloudAPL, SaleorCloudAPLConfig } from "./saleor-cloud-apl";
|
||||||
|
|
||||||
|
const fetchMock = vi.fn();
|
||||||
|
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
const aplConfig: SaleorCloudAPLConfig = {
|
||||||
|
resourceUrl: "https://example.com",
|
||||||
|
token: "token",
|
||||||
|
};
|
||||||
|
|
||||||
|
const stubAuthData: AuthData = {
|
||||||
|
domain: "example.com",
|
||||||
|
token: "example-token",
|
||||||
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
|
appId: "42",
|
||||||
|
jwks: "{}",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("APL", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.resetModules();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("SaleorCloudAPL", () => {
|
||||||
|
describe("set", () => {
|
||||||
|
it("Successful save of the auth data", async () => {
|
||||||
|
fetchMock.mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({ result: "ok" }),
|
||||||
|
ok: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apl = new SaleorCloudAPL(aplConfig);
|
||||||
|
await apl.set(stubAuthData);
|
||||||
|
|
||||||
|
expect(fetchMock).toBeCalledWith(
|
||||||
|
"https://example.com",
|
||||||
|
|
||||||
|
{
|
||||||
|
body: JSON.stringify({
|
||||||
|
saleor_app_id: "42",
|
||||||
|
saleor_api_url: "https://example.com/graphql/",
|
||||||
|
jwks: "{}",
|
||||||
|
domain: "example.com",
|
||||||
|
token: "example-token",
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer token",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Raise error when register service returns non 200 response", async () => {
|
||||||
|
fetchMock.mockResolvedValue({
|
||||||
|
status: 500,
|
||||||
|
ok: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apl = new SaleorCloudAPL(aplConfig);
|
||||||
|
|
||||||
|
await expect(apl.set(stubAuthData)).rejects.toThrow(
|
||||||
|
"Fetch returned with non 200 status code 500"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("get", () => {
|
||||||
|
describe("Read existing auth data", () => {
|
||||||
|
it("Read existing auth data", async () => {
|
||||||
|
fetchMock.mockResolvedValue({
|
||||||
|
status: 200,
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
saleor_app_id: stubAuthData.appId,
|
||||||
|
saleor_api_url: stubAuthData.saleorApiUrl,
|
||||||
|
jwks: stubAuthData.jwks,
|
||||||
|
domain: stubAuthData.domain,
|
||||||
|
token: stubAuthData.token,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const apl = new SaleorCloudAPL(aplConfig);
|
||||||
|
|
||||||
|
expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);
|
||||||
|
|
||||||
|
expect(fetchMock).toBeCalledWith(
|
||||||
|
"https://example.com/aHR0cHM6Ly9leGFtcGxlLmNvbS9ncmFwaHFsLw", // base64 encoded api url
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer token",
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Return undefined when unknown domain requested", async () => {
|
||||||
|
fetchMock.mockResolvedValue({
|
||||||
|
status: 404,
|
||||||
|
ok: false,
|
||||||
|
json: async () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apl = new SaleorCloudAPL(aplConfig);
|
||||||
|
|
||||||
|
expect(await apl.get("http://unknown-domain.example.com/graphql/")).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,11 @@ export type SaleorCloudAPLConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateResponseStatus = (response: Response) => {
|
const validateResponseStatus = (response: Response) => {
|
||||||
|
if (response.status === 404) {
|
||||||
|
debug("Auth data not found");
|
||||||
|
|
||||||
|
throw new Error("Auth data not found");
|
||||||
|
}
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
debug("Response failed with status %s", response.status);
|
debug("Response failed with status %s", response.status);
|
||||||
|
|
||||||
|
@ -56,8 +61,8 @@ export class SaleorCloudAPL implements APL {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUrlForDomain(saleorApiUrl: string) {
|
private getUrlForDomain(saleorApiUrl: string) {
|
||||||
// API URL has to be base64 encoded
|
// API URL has to be base64url encoded
|
||||||
return `${this.resourceUrl}/${btoa(saleorApiUrl)}`;
|
return `${this.resourceUrl}/${Buffer.from(saleorApiUrl).toString("base64url")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(saleorApiUrl: string): Promise<AuthData | undefined> {
|
async get(saleorApiUrl: string): Promise<AuthData | undefined> {
|
||||||
|
@ -68,10 +73,20 @@ export class SaleorCloudAPL implements APL {
|
||||||
headers: { "Content-Type": "application/json", ...this.headers },
|
headers: { "Content-Type": "application/json", ...this.headers },
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
debug("Failed to reach API call: %s", error?.message ?? "Unknown error");
|
debug("Failed to reach API call: %s", error?.message ?? "Unknown error");
|
||||||
throw new Error(`Attempt in fetch the data resulted with error: ${error}`);
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
debug("No response from the API");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
validateResponseStatus(response);
|
validateResponseStatus(response);
|
||||||
|
} catch {
|
||||||
|
debug("Response status not valid");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const parsedResponse = (await response.json().catch((e) => {
|
const parsedResponse = (await response.json().catch((e) => {
|
||||||
debug("Failed to parse response: %s", e?.message ?? "Unknown error");
|
debug("Failed to parse response: %s", e?.message ?? "Unknown error");
|
||||||
|
|
Loading…
Reference in a new issue