Rename apiUrl to saleorApiUrl for consistency (#144)
This commit is contained in:
parent
62bdb80385
commit
c4fa6a1c05
24 changed files with 92 additions and 86 deletions
18
docs/apl.md
18
docs/apl.md
|
@ -4,11 +4,11 @@ APL is an interface for managing auth data of registered Apps. Implementing it d
|
||||||
|
|
||||||
## Available methods
|
## Available methods
|
||||||
|
|
||||||
- `get: (apiUrl: string) => Promise<AuthData | undefined>` - If the entry for given apiUrl exists, returns AuthData object.
|
- `get: (saleorApiUrl: string) => Promise<AuthData | undefined>` - If the entry for given saleorApiUrl exists, returns AuthData object.
|
||||||
|
|
||||||
- `set: (authData: AuthData) => Promise<void>` - Save auth data.
|
- `set: (authData: AuthData) => Promise<void>` - Save auth data.
|
||||||
|
|
||||||
- `delete: (apiUrl: string) => Promise<void>` - Remove auth data fot the given API URL.
|
- `delete: (saleorApiUrl: string) => Promise<void>` - Remove auth data fot the given API URL.
|
||||||
|
|
||||||
- `getAll: () => Promise<AuthData[]>` - Returns all auth data available.
|
- `getAll: () => Promise<AuthData[]>` - Returns all auth data available.
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ Interface containing data used for communication with the Saleor API:
|
||||||
export interface AuthData {
|
export interface AuthData {
|
||||||
domain: string;
|
domain: string;
|
||||||
token: string;
|
token: string;
|
||||||
apiUrl: string;
|
saleorApiUrl: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
jwks: string;
|
jwks: string;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ export interface AuthData {
|
||||||
|
|
||||||
- `domain` - Domain of the API
|
- `domain` - Domain of the API
|
||||||
- `token` - Authorization token
|
- `token` - Authorization token
|
||||||
- `apiUrl` - Full URL to the Saleor GraphQL API
|
- `saleorApiUrl` - Full URL to the Saleor GraphQL API
|
||||||
- `appID` - ID of the app assigned during the installation process
|
- `appID` - ID of the app assigned during the installation process
|
||||||
- `jwks` - JSON Web Key Set available at `https://<your-saleor-domain>/.well-known/jwks.json`, cached in the APL for the faster webhook validation
|
- `jwks` - JSON Web Key Set available at `https://<your-saleor-domain>/.well-known/jwks.json`, cached in the APL for the faster webhook validation
|
||||||
|
|
||||||
|
@ -72,18 +72,18 @@ const client = createClient();
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|
||||||
const redisAPL: APL = {
|
const redisAPL: APL = {
|
||||||
get: async (apiUrl: string) => {
|
get: async (saleorApiUrl: string) => {
|
||||||
const response = await client.get(apiUrl);
|
const response = await client.get(saleorApiUrl);
|
||||||
if (response) {
|
if (response) {
|
||||||
return JSON.parse(response);
|
return JSON.parse(response);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
set: async (authData: AuthData) => {
|
set: async (authData: AuthData) => {
|
||||||
await client.set(authData.apiUrl, JSON.stringify(authData));
|
await client.set(authData.saleorApiUrl, JSON.stringify(authData));
|
||||||
},
|
},
|
||||||
delete: async (apiUrl: string) => {
|
delete: async (saleorApiUrl: string) => {
|
||||||
await client.del(apiUrl);
|
await client.del(saleorApiUrl);
|
||||||
},
|
},
|
||||||
getAll: async () => {
|
getAll: async () => {
|
||||||
throw new Exception("Not implemented.");
|
throw new Exception("Not implemented.");
|
||||||
|
|
|
@ -53,7 +53,7 @@ fetch("/api/protected", {
|
||||||
* headers the backend will check if the request has enough permissions to
|
* headers the backend will check if the request has enough permissions to
|
||||||
* perform the action.
|
* perform the action.
|
||||||
*/
|
*/
|
||||||
"saleor-api-url": apiUrl,
|
"saleor-api-url": saleorApiUrl,
|
||||||
"authorization-bearer": token,
|
"authorization-bearer": token,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export interface AuthData {
|
export interface AuthData {
|
||||||
domain: string;
|
domain: string;
|
||||||
token: string;
|
token: string;
|
||||||
apiUrl: string;
|
saleorApiUrl: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
jwks: string;
|
jwks: string;
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,9 @@ export type AplConfiguredResult =
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface APL {
|
export interface APL {
|
||||||
get: (apiUrl: string) => Promise<AuthData | undefined>;
|
get: (saleorApiUrl: string) => Promise<AuthData | undefined>;
|
||||||
set: (authData: AuthData) => Promise<void>;
|
set: (authData: AuthData) => Promise<void>;
|
||||||
delete: (apiUrl: string) => Promise<void>;
|
delete: (saleorApiUrl: string) => Promise<void>;
|
||||||
getAll: () => Promise<AuthData[]>;
|
getAll: () => Promise<AuthData[]>;
|
||||||
/**
|
/**
|
||||||
* Inform that configuration is finished and correct
|
* Inform that configuration is finished and correct
|
||||||
|
|
|
@ -12,9 +12,9 @@ export const authDataFromObject = (parsed: unknown): AuthData | undefined => {
|
||||||
debug("Given object did not contained AuthData");
|
debug("Given object did not contained AuthData");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const { apiUrl, appId, domain, token, jwks } = parsed as AuthData;
|
const { saleorApiUrl, appId, domain, token, jwks } = parsed as AuthData;
|
||||||
return {
|
return {
|
||||||
apiUrl,
|
saleorApiUrl,
|
||||||
appId,
|
appId,
|
||||||
domain,
|
domain,
|
||||||
token,
|
token,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { FileAPL } from "./file-apl";
|
||||||
const stubAuthData: AuthData = {
|
const stubAuthData: AuthData = {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "example-token",
|
token: "example-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ describe("APL", () => {
|
||||||
vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON");
|
vi.spyOn(fsPromises, "readFile").mockResolvedValue("Not a valid JSON");
|
||||||
|
|
||||||
const apl = new FileAPL();
|
const apl = new FileAPL();
|
||||||
await expect(apl.get(stubAuthData.apiUrl)).resolves.toBe(undefined);
|
await expect(apl.get(stubAuthData.saleorApiUrl)).resolves.toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns auth data for existing api url", async () => {
|
it("Returns auth data for existing api url", async () => {
|
||||||
|
@ -31,7 +31,7 @@ describe("APL", () => {
|
||||||
|
|
||||||
const apl = new FileAPL();
|
const apl = new FileAPL();
|
||||||
|
|
||||||
expect(await apl.get(stubAuthData.apiUrl)).toStrictEqual(stubAuthData);
|
expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns undefined for unknown api url", async () => {
|
it("Returns undefined for unknown api url", async () => {
|
||||||
|
@ -75,7 +75,7 @@ describe("APL", () => {
|
||||||
|
|
||||||
const apl = new FileAPL();
|
const apl = new FileAPL();
|
||||||
|
|
||||||
await apl.delete(stubAuthData.apiUrl);
|
await apl.delete(stubAuthData.saleorApiUrl);
|
||||||
|
|
||||||
expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", "{}");
|
expect(spyWriteFile).toBeCalledWith(".saleor-app-auth.json", "{}");
|
||||||
});
|
});
|
||||||
|
|
|
@ -48,11 +48,11 @@ export class FileAPL implements APL {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { token, domain, apiUrl, appId, jwks } = parsedData;
|
const { token, domain, saleorApiUrl, appId, jwks } = parsedData;
|
||||||
|
|
||||||
if (token && domain && apiUrl && appId && jwks) {
|
if (token && domain && saleorApiUrl && appId && jwks) {
|
||||||
debug("Token and domain found, returning values: %s, %s", domain, `${token[0]}***`);
|
debug("Token and domain found, returning values: %s, %s", domain, `${token[0]}***`);
|
||||||
return { token, domain, apiUrl, appId, jwks };
|
return { token, domain, saleorApiUrl, appId, jwks };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -77,9 +77,9 @@ export class FileAPL implements APL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(apiUrl: string) {
|
async get(saleorApiUrl: string) {
|
||||||
const authData = await this.loadDataFromFile();
|
const authData = await this.loadDataFromFile();
|
||||||
if (apiUrl === authData?.apiUrl) {
|
if (saleorApiUrl === authData?.saleorApiUrl) {
|
||||||
return authData;
|
return authData;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -89,10 +89,10 @@ export class FileAPL implements APL {
|
||||||
await this.saveDataToFile(authData);
|
await this.saveDataToFile(authData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(apiUrl: string) {
|
async delete(saleorApiUrl: string) {
|
||||||
const authData = await this.loadDataFromFile();
|
const authData = await this.loadDataFromFile();
|
||||||
|
|
||||||
if (apiUrl === authData?.apiUrl) {
|
if (saleorApiUrl === authData?.saleorApiUrl) {
|
||||||
await this.saveDataToFile();
|
await this.saveDataToFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,4 @@ export const hasAuthData = (data: unknown) =>
|
||||||
hasProp(data, "domain") &&
|
hasProp(data, "domain") &&
|
||||||
hasProp(data, "token") &&
|
hasProp(data, "token") &&
|
||||||
hasProp(data, "appId") &&
|
hasProp(data, "appId") &&
|
||||||
hasProp(data, "apiUrl");
|
hasProp(data, "saleorApiUrl");
|
||||||
|
|
|
@ -19,7 +19,7 @@ const validateResponseStatus = (response: Response) => {
|
||||||
|
|
||||||
const mapAuthDataToAPIBody = (authData: AuthData) => ({
|
const mapAuthDataToAPIBody = (authData: AuthData) => ({
|
||||||
saleor_app_id: authData.appId,
|
saleor_app_id: authData.appId,
|
||||||
api_url: authData.apiUrl,
|
api_url: authData.saleorApiUrl,
|
||||||
jwks: authData.jwks,
|
jwks: authData.jwks,
|
||||||
domain: authData.domain,
|
domain: authData.domain,
|
||||||
token: authData.token,
|
token: authData.token,
|
||||||
|
@ -46,15 +46,15 @@ export class SaleorCloudAPL implements APL {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUrlForDomain(apiUrl: string) {
|
private getUrlForDomain(saleorApiUrl: string) {
|
||||||
// API URL has to be base64 encoded
|
// API URL has to be base64 encoded
|
||||||
return `${this.resourceUrl}/${btoa(apiUrl)}`;
|
return `${this.resourceUrl}/${btoa(saleorApiUrl)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(apiUrl: string): Promise<AuthData | undefined> {
|
async get(saleorApiUrl: string): Promise<AuthData | undefined> {
|
||||||
debug("Will fetch data from SaleorCloudAPL for apiUrl %s", apiUrl);
|
debug("Will fetch data from SaleorCloudAPL for saleorApiUrl %s", saleorApiUrl);
|
||||||
|
|
||||||
const response = await fetch(this.getUrlForDomain(apiUrl), {
|
const response = await fetch(this.getUrlForDomain(saleorApiUrl), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { "Content-Type": "application/json", ...this.headers },
|
headers: { "Content-Type": "application/json", ...this.headers },
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
@ -70,7 +70,7 @@ export class SaleorCloudAPL implements APL {
|
||||||
|
|
||||||
const authData = authDataFromObject(parsedResponse);
|
const authData = authDataFromObject(parsedResponse);
|
||||||
if (!authData) {
|
if (!authData) {
|
||||||
debug("No auth data for given apiUrl");
|
debug("No auth data for given saleorApiUrl");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +97,11 @@ export class SaleorCloudAPL implements APL {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(apiUrl: string) {
|
async delete(saleorApiUrl: string) {
|
||||||
debug("Deleting data from SaleorCloud for apiUrl: %s", apiUrl);
|
debug("Deleting data from SaleorCloud for saleorApiUrl: %s", saleorApiUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.getUrlForDomain(apiUrl), {
|
const response = await fetch(this.getUrlForDomain(saleorApiUrl), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { "Content-Type": "application/json", ...this.headers },
|
headers: { "Content-Type": "application/json", ...this.headers },
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ const aplConfig: UpstashAPLConfig = {
|
||||||
const stubAuthData: AuthData = {
|
const stubAuthData: AuthData = {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "example-token",
|
token: "example-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,7 @@ describe("APL", () => {
|
||||||
|
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line quotes
|
// eslint-disable-next-line quotes
|
||||||
body: `["SET", "${stubAuthData.apiUrl}", "${JSON.stringify(stubAuthData)}"]`,
|
body: `["SET", "${stubAuthData.saleorApiUrl}", "${JSON.stringify(stubAuthData)}"]`,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: "Bearer token",
|
Authorization: "Bearer token",
|
||||||
|
@ -93,7 +93,7 @@ describe("APL", () => {
|
||||||
});
|
});
|
||||||
const apl = new UpstashAPL(aplConfig);
|
const apl = new UpstashAPL(aplConfig);
|
||||||
|
|
||||||
expect(await apl.get(stubAuthData.apiUrl)).toStrictEqual(stubAuthData);
|
expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Return undefined when unknown domain requested", async () => {
|
it("Return undefined when unknown domain requested", async () => {
|
||||||
|
|
|
@ -87,20 +87,20 @@ export class UpstashAPL implements APL {
|
||||||
|
|
||||||
private async saveDataToUpstash(authData: AuthData) {
|
private async saveDataToUpstash(authData: AuthData) {
|
||||||
debug("saveDataToUpstash() called with: %j", {
|
debug("saveDataToUpstash() called with: %j", {
|
||||||
apiUrl: authData.apiUrl,
|
saleorApiUrl: authData.saleorApiUrl,
|
||||||
token: authData.token.substring(0, 4),
|
token: authData.token.substring(0, 4),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = JSON.stringify(authData);
|
const data = JSON.stringify(authData);
|
||||||
await this.upstashRequest(`["SET", "${authData.apiUrl}", "${data}"]`);
|
await this.upstashRequest(`["SET", "${authData.saleorApiUrl}", "${data}"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async deleteDataFromUpstash(apiUrl: string) {
|
private async deleteDataFromUpstash(saleorApiUrl: string) {
|
||||||
await this.upstashRequest(`["DEL", "${apiUrl}"]`);
|
await this.upstashRequest(`["DEL", "${saleorApiUrl}"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchDataFromUpstash(apiUrl: string) {
|
private async fetchDataFromUpstash(saleorApiUrl: string) {
|
||||||
const result = await this.upstashRequest(`["GET", "${apiUrl}"]`);
|
const result = await this.upstashRequest(`["GET", "${saleorApiUrl}"]`);
|
||||||
if (result) {
|
if (result) {
|
||||||
const authData = JSON.parse(result);
|
const authData = JSON.parse(result);
|
||||||
return authData;
|
return authData;
|
||||||
|
@ -108,16 +108,16 @@ export class UpstashAPL implements APL {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(apiUrl: string) {
|
async get(saleorApiUrl: string) {
|
||||||
return this.fetchDataFromUpstash(apiUrl);
|
return this.fetchDataFromUpstash(saleorApiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(authData: AuthData) {
|
async set(authData: AuthData) {
|
||||||
await this.saveDataToUpstash(authData);
|
await this.saveDataToUpstash(authData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(apiUrl: string) {
|
async delete(saleorApiUrl: string) {
|
||||||
await this.deleteDataFromUpstash(apiUrl);
|
await this.deleteDataFromUpstash(saleorApiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll() {
|
async getAll() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ const aplConfig = {
|
||||||
const stubAuthData: AuthData = {
|
const stubAuthData: AuthData = {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "example-token",
|
token: "example-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
};
|
};
|
||||||
|
@ -150,7 +150,7 @@ describe("APL", () => {
|
||||||
|
|
||||||
const apl = new VercelAPL(aplConfig);
|
const apl = new VercelAPL(aplConfig);
|
||||||
|
|
||||||
expect(await apl.get(stubAuthData.apiUrl)).toStrictEqual(stubAuthData);
|
expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Return undefined when unknown api url requested", async () => {
|
it("Return undefined when unknown api url requested", async () => {
|
||||||
|
|
|
@ -108,10 +108,10 @@ export class VercelAPL implements APL {
|
||||||
debug("Register service responded successfully");
|
debug("Register service responded successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(apiUrl: string) {
|
async get(saleorApiUrl: string) {
|
||||||
const authData = getEnvAuth();
|
const authData = getEnvAuth();
|
||||||
|
|
||||||
if (authData && apiUrl === authData.apiUrl) {
|
if (authData && saleorApiUrl === authData.saleorApiUrl) {
|
||||||
return authData;
|
return authData;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -126,8 +126,8 @@ export class VercelAPL implements APL {
|
||||||
await this.saveDataToVercel(authData);
|
await this.saveDataToVercel(authData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(apiUrl: string) {
|
async delete(saleorApiUrl: string) {
|
||||||
if (apiUrl === getEnvAuth()?.apiUrl) {
|
if (saleorApiUrl === getEnvAuth()?.saleorApiUrl) {
|
||||||
// Override existing data with the empty values
|
// Override existing data with the empty values
|
||||||
await this.saveDataToVercel();
|
await this.saveDataToVercel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { DashboardEventFactory } from "./events";
|
||||||
|
|
||||||
const origin = "http://example.com";
|
const origin = "http://example.com";
|
||||||
const domain = "saleor.domain.host";
|
const domain = "saleor.domain.host";
|
||||||
const apiUrl = "https://saleor.domain.host/graphql/";
|
const saleorApiUrl = "https://saleor.domain.host/graphql/";
|
||||||
|
|
||||||
Object.defineProperty(window.document, "referrer", {
|
Object.defineProperty(window.document, "referrer", {
|
||||||
value: origin,
|
value: origin,
|
||||||
|
@ -18,7 +18,7 @@ Object.defineProperty(window.document, "referrer", {
|
||||||
|
|
||||||
Object.defineProperty(window, "location", {
|
Object.defineProperty(window, "location", {
|
||||||
value: {
|
value: {
|
||||||
href: `${origin}?${AppIframeParams.DOMAIN}=${domain}&${AppIframeParams.APP_ID}=appid&${AppIframeParams.SALEOR_API_URL}=${apiUrl}`,
|
href: `${origin}?${AppIframeParams.DOMAIN}=${domain}&${AppIframeParams.APP_ID}=appid&${AppIframeParams.SALEOR_API_URL}=${saleorApiUrl}`,
|
||||||
},
|
},
|
||||||
writable: true,
|
writable: true,
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,7 @@ describe("AppBridgeProvider", () => {
|
||||||
appBridgeInstance={
|
appBridgeInstance={
|
||||||
new AppBridge({
|
new AppBridge({
|
||||||
targetDomain: domain,
|
targetDomain: domain,
|
||||||
saleorApiUrl: apiUrl,
|
saleorApiUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -102,7 +102,7 @@ describe("useAppBridge hook", () => {
|
||||||
it("Stores active state in React State", () => {
|
it("Stores active state in React State", () => {
|
||||||
const appBridge = new AppBridge({
|
const appBridge = new AppBridge({
|
||||||
targetDomain: domain,
|
targetDomain: domain,
|
||||||
saleorApiUrl: apiUrl,
|
saleorApiUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderCallback = vi.fn();
|
const renderCallback = vi.fn();
|
||||||
|
@ -138,7 +138,7 @@ describe("useAppBridge hook", () => {
|
||||||
ready: false,
|
ready: false,
|
||||||
theme: "light",
|
theme: "light",
|
||||||
locale: "en",
|
locale: "en",
|
||||||
saleorApiUrl: apiUrl,
|
saleorApiUrl,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -207,7 +207,7 @@ export class AppBridge {
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (!window.parent) {
|
if (!window.parent) {
|
||||||
debug("window.parent doesnt exist, will throw");
|
debug("window.parent doesn't exist, will throw");
|
||||||
|
|
||||||
reject(new Error("Parent window does not exist."));
|
reject(new Error("Parent window does not exist."));
|
||||||
return;
|
return;
|
||||||
|
@ -305,7 +305,7 @@ export class AppBridge {
|
||||||
debug("Received message from origin: %s and data: %j", origin, data);
|
debug("Received message from origin: %s and data: %j", origin, data);
|
||||||
|
|
||||||
if (origin !== this.refererOrigin) {
|
if (origin !== this.refererOrigin) {
|
||||||
debug("Origin from message doesnt match refererOrigin. Function will return now");
|
debug("Origin from message doesn't match refererOrigin. Function will return now");
|
||||||
// TODO what should happen here - be explicit
|
// TODO what should happen here - be explicit
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,16 @@ type GetIdResponseType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GetAppIdProperties {
|
export interface GetAppIdProperties {
|
||||||
apiUrl: string;
|
saleorApiUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppId = async ({
|
export const getAppId = async ({
|
||||||
apiUrl,
|
saleorApiUrl,
|
||||||
token,
|
token,
|
||||||
}: GetAppIdProperties): Promise<string | undefined> => {
|
}: GetAppIdProperties): Promise<string | undefined> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(saleorApiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
@ -54,7 +54,7 @@ describe("create-app-register-handler", () => {
|
||||||
* It fails -> params.auth_token isn't present
|
* It fails -> params.auth_token isn't present
|
||||||
*/
|
*/
|
||||||
expect(mockApl.set).toHaveBeenCalledWith({
|
expect(mockApl.set).toHaveBeenCalledWith({
|
||||||
apiUrl: "https://mock-saleor-domain.saleor.cloud/graphql/",
|
saleorApiUrl: "https://mock-saleor-domain.saleor.cloud/graphql/",
|
||||||
domain: "https://mock-saleor-domain.saleor.cloud/",
|
domain: "https://mock-saleor-domain.saleor.cloud/",
|
||||||
token: "mock-auth-token",
|
token: "mock-auth-token",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const createAppRegisterHandler = ({ apl }: CreateAppRegisterHandlerOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get App ID from the API, to confirm that communication can be established
|
// Try to get App ID from the API, to confirm that communication can be established
|
||||||
const appId = await getAppId({ apiUrl: saleorApiUrl, token: authToken });
|
const appId = await getAppId({ saleorApiUrl, token: authToken });
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
return new Response(
|
return new Response(
|
||||||
{
|
{
|
||||||
|
@ -80,7 +80,13 @@ export const createAppRegisterHandler = ({ apl }: CreateAppRegisterHandlerOption
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apl.set({ domain: saleorDomain, token: authToken, apiUrl: saleorApiUrl, appId, jwks });
|
await apl.set({
|
||||||
|
domain: saleorDomain,
|
||||||
|
token: authToken,
|
||||||
|
saleorApiUrl,
|
||||||
|
appId,
|
||||||
|
jwks,
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
debug("There was an error during saving the auth data");
|
debug("There was an error during saving the auth data");
|
||||||
return Response.InternalServerError({
|
return Response.InternalServerError({
|
||||||
|
|
|
@ -33,12 +33,12 @@ describe("processAsyncSaleorWebhook", () => {
|
||||||
let mockRequest: NextApiRequest;
|
let mockRequest: NextApiRequest;
|
||||||
|
|
||||||
const mockAPL: APL = {
|
const mockAPL: APL = {
|
||||||
get: async (apiUrl: string) =>
|
get: async (saleorApiUrl: string) =>
|
||||||
apiUrl === "https://example.com/graphql/"
|
saleorApiUrl === "https://example.com/graphql/"
|
||||||
? {
|
? {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "mock-token",
|
token: "mock-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ export const processAsyncSaleorWebhook: ProcessAsyncSaleorWebhook = async <T>({
|
||||||
await verifySignatureWithJwks(authData.jwks, signature, rawBody);
|
await verifySignatureWithJwks(authData.jwks, signature, rawBody);
|
||||||
} catch {
|
} catch {
|
||||||
debug("Request signature check failed. Refresh the JWKS cache and check again");
|
debug("Request signature check failed. Refresh the JWKS cache and check again");
|
||||||
const newJwks = await fetchRemoteJwks(authData.apiUrl);
|
const newJwks = await fetchRemoteJwks(authData.saleorApiUrl);
|
||||||
try {
|
try {
|
||||||
debug("Second attempt to validate the signature JWKS, using fresh tokens from the API");
|
debug("Second attempt to validate the signature JWKS, using fresh tokens from the API");
|
||||||
await verifySignatureWithJwks(newJwks, signature, rawBody);
|
await verifySignatureWithJwks(newJwks, signature, rawBody);
|
||||||
|
|
|
@ -26,12 +26,12 @@ describe("processSaleorProtectedHandler", () => {
|
||||||
let mockRequest: NextApiRequest;
|
let mockRequest: NextApiRequest;
|
||||||
|
|
||||||
const mockAPL: APL = {
|
const mockAPL: APL = {
|
||||||
get: async (apiUrl: string) =>
|
get: async (saleorApiUrl: string) =>
|
||||||
apiUrl === "https://example.com/graphql/"
|
saleorApiUrl === "https://example.com/graphql/"
|
||||||
? {
|
? {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "mock-token",
|
token: "mock-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ describe("processSaleorProtectedHandler", () => {
|
||||||
authData: {
|
authData: {
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
token: "mock-token",
|
token: "mock-token",
|
||||||
apiUrl: "https://example.com/graphql/",
|
saleorApiUrl: "https://example.com/graphql/",
|
||||||
appId: "42",
|
appId: "42",
|
||||||
jwks: "{}",
|
jwks: "{}",
|
||||||
},
|
},
|
||||||
|
|
|
@ -85,7 +85,7 @@ export const processSaleorProtectedHandler: ProcessAsyncSaleorProtectedHandler =
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await verifyJWT({ appId: authData.appId, token, apiUrl: saleorApiUrl });
|
await verifyJWT({ appId: authData.appId, token, saleorApiUrl });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
|
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,18 +27,18 @@ describe("verifyJWT", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Process valid request", async () => {
|
it("Process valid request", async () => {
|
||||||
await verifyJWT({ appId: validAppId, apiUrl: validApiUrl, token: validToken });
|
await verifyJWT({ appId: validAppId, saleorApiUrl: validApiUrl, token: validToken });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Throw error on decode issue", async () => {
|
it("Throw error on decode issue", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
verifyJWT({ appId: validAppId, apiUrl: validApiUrl, token: "wrong_token" })
|
verifyJWT({ appId: validAppId, saleorApiUrl: validApiUrl, token: "wrong_token" })
|
||||||
).rejects.toThrow("JWT verification failed: Could not decode authorization token.");
|
).rejects.toThrow("JWT verification failed: Could not decode authorization token.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Throw error on app ID missmatch", async () => {
|
it("Throw error on app ID missmatch", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
verifyJWT({ appId: "wrong_id", apiUrl: validApiUrl, token: validToken })
|
verifyJWT({ appId: "wrong_id", saleorApiUrl: validApiUrl, token: validToken })
|
||||||
).rejects.toThrow("JWT verification failed: Token's app property is different than app ID.");
|
).rejects.toThrow("JWT verification failed: Token's app property is different than app ID.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,11 +11,11 @@ export interface DashboardTokenPayload extends jose.JWTPayload {
|
||||||
|
|
||||||
export interface verifyJWTArguments {
|
export interface verifyJWTArguments {
|
||||||
appId: string;
|
appId: string;
|
||||||
apiUrl: string;
|
saleorApiUrl: string;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const verifyJWT = async ({ apiUrl, token, appId }: verifyJWTArguments) => {
|
export const verifyJWT = async ({ saleorApiUrl, token, appId }: verifyJWTArguments) => {
|
||||||
let tokenClaims: DashboardTokenPayload;
|
let tokenClaims: DashboardTokenPayload;
|
||||||
const ERROR_MESSAGE = "JWT verification failed:";
|
const ERROR_MESSAGE = "JWT verification failed:";
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export const verifyJWT = async ({ apiUrl, token, appId }: verifyJWTArguments) =>
|
||||||
try {
|
try {
|
||||||
debug("Trying to create JWKS");
|
debug("Trying to create JWKS");
|
||||||
|
|
||||||
const JWKS = jose.createRemoteJWKSet(new URL(getJwksUrlFromSaleorApiUrl(apiUrl)));
|
const JWKS = jose.createRemoteJWKSet(new URL(getJwksUrlFromSaleorApiUrl(saleorApiUrl)));
|
||||||
debug("Trying to compare JWKS with token");
|
debug("Trying to compare JWKS with token");
|
||||||
await jose.jwtVerify(token, JWKS);
|
await jose.jwtVerify(token, JWKS);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const verifySignature = async (domain: string, signature: string, rawBody
|
||||||
* https://docs.saleor.io/docs/3.x/developer/extending/apps/asynchronous-webhooks#payload-signature
|
* https://docs.saleor.io/docs/3.x/developer/extending/apps/asynchronous-webhooks#payload-signature
|
||||||
*/
|
*/
|
||||||
export const verifySignatureFromApiUrl = async (
|
export const verifySignatureFromApiUrl = async (
|
||||||
apiUrl: string,
|
saleorApiUrl: string,
|
||||||
signature: string,
|
signature: string,
|
||||||
rawBody: string
|
rawBody: string
|
||||||
) => {
|
) => {
|
||||||
|
@ -49,7 +49,7 @@ export const verifySignatureFromApiUrl = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const remoteJwks = jose.createRemoteJWKSet(
|
const remoteJwks = jose.createRemoteJWKSet(
|
||||||
new URL(getJwksUrlFromSaleorApiUrl(apiUrl))
|
new URL(getJwksUrlFromSaleorApiUrl(saleorApiUrl))
|
||||||
) as jose.FlattenedVerifyGetKey;
|
) as jose.FlattenedVerifyGetKey;
|
||||||
|
|
||||||
debug("Created remote JWKS");
|
debug("Created remote JWKS");
|
||||||
|
|
Loading…
Reference in a new issue