Skip to content

Commit

Permalink
Custom auth header in GraphiQL Playground (#5336)
Browse files Browse the repository at this point in the history
* feature: custom auth header in graphiql

* handle lowercase

---------

Co-authored-by: Patryk Andrzejewski <[email protected]>
  • Loading branch information
2 people authored and poulch committed Jan 10, 2025
1 parent d718b9c commit b44516b
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-owls-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---

You can now use custom auth headers in graphiql dev mode panel.
12 changes: 4 additions & 8 deletions src/components/DevModePanel/DevModePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
import { useDashboardTheme } from "@dashboard/components/GraphiQL/styles";
import { DashboardModal } from "@dashboard/components/Modal";
import { useOnboarding } from "@dashboard/welcomePage/WelcomePageOnboarding/onboardingContext";
import { createGraphiQLFetcher, FetcherOpts, FetcherParams } from "@graphiql/toolkit";
import { createFetch } from "@saleor/sdk";
import { FetcherOpts, FetcherParams } from "@graphiql/toolkit";
import React from "react";
import { useIntl } from "react-intl";

Expand All @@ -12,24 +11,21 @@ import { useContextualLink } from "../AppLayout/ContextualLinks/useContextualLin
import PlainGraphiQL from "../GraphiQLPlain";
import { useDevModeContext } from "./hooks";
import { messages } from "./messages";

const authorizedFetch = createFetch();
import { getFetcher } from "./utils";

export const DevModePanel: React.FC = () => {
const intl = useIntl();
const subtitle = useContextualLink("dev_panel");
const { rootStyle } = useDashboardTheme();
const { markOnboardingStepAsCompleted } = useOnboarding();
const { isDevModeVisible, variables, devModeContent, setDevModeVisibility } = useDevModeContext();
const baseFetcher = createGraphiQLFetcher({
url: process.env.API_URL,
fetch: authorizedFetch,
});
const fetcher = async (graphQLParams: FetcherParams, opts: FetcherOpts) => {
if (graphQLParams.operationName !== "IntrospectionQuery") {
markOnboardingStepAsCompleted("graphql-playground");
}

const baseFetcher = getFetcher(opts);

const result = await baseFetcher(graphQLParams, opts); // Call the base fetcher

return result;
Expand Down
95 changes: 95 additions & 0 deletions src/components/DevModePanel/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { createGraphiQLFetcher, FetcherOpts } from "@graphiql/toolkit";
import { createFetch } from "@saleor/sdk";

import { getFetcher } from "./utils";

jest.mock("@graphiql/toolkit", () => ({
createGraphiQLFetcher: jest.fn(),
}));

jest.mock("@saleor/sdk", () => ({
createFetch: jest.fn().mockReturnValue(jest.fn()),
}));

const mockCreateGraphiQLFetcher = createGraphiQLFetcher as jest.Mock;
const authorizedFetch = createFetch as jest.Mock;

describe("getFetcher", () => {
const mockApiUrl = "http://test-api.com";
let originalFetch: typeof fetch;

beforeEach(() => {
process.env.API_URL = mockApiUrl;
originalFetch = global.fetch;
});

afterEach(() => {
jest.resetAllMocks();
global.fetch = originalFetch;
});

it("should return fetcher with authorizedFetch when no auth headers", () => {
// Arrange
const opts: FetcherOpts = { headers: {} };

// Act
getFetcher(opts);

// Assert
expect(authorizedFetch).toHaveBeenCalled();
// 'toHaveBeenCalledWith' can't properly compare mock functions
expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith(
expect.objectContaining({
url: mockApiUrl,
}),
);
});

it("should return fetcher with fetch when Authorization header present", () => {
// Arrange
const opts: FetcherOpts = {
headers: { Authorization: "Bearer token" },
};

// Act
getFetcher(opts);

// Assert
expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({
url: mockApiUrl,
fetch: fetch,
});
});

it("should return fetcher with fetch when Authorization-Bearer header present", () => {
// Arrange
const opts: FetcherOpts = {
headers: { "Authorization-Bearer": "token" },
};

// Act
getFetcher(opts);

// Assert
expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({
url: mockApiUrl,
fetch: fetch,
});
});

it("should return fetcher with fetch when lowercase header present", () => {
// Arrange
const opts: FetcherOpts = {
headers: { "authorization-bearer": "token" },
};

// Act
getFetcher(opts);

// Assert
expect(mockCreateGraphiQLFetcher).toHaveBeenCalledWith({
url: mockApiUrl,
fetch: fetch,
});
});
});
23 changes: 23 additions & 0 deletions src/components/DevModePanel/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createGraphiQLFetcher, FetcherOpts } from "@graphiql/toolkit";
import { createFetch } from "@saleor/sdk";

const authHeaders = ["Authorization", "Authorization-Bearer"];

const authorizedFetch = createFetch();

export const getFetcher = (opts: FetcherOpts) => {
let httpFetch = authorizedFetch;

const hasAuthorizationHeaders =
opts.headers &&
authHeaders.some(header => opts.headers![header] || opts.headers![header.toLowerCase()]);

if (hasAuthorizationHeaders) {
httpFetch = fetch;
}

return createGraphiQLFetcher({
url: process.env.API_URL as string,
fetch: httpFetch as typeof fetch,
});
};

0 comments on commit b44516b

Please sign in to comment.