Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/basic auth #608

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-cheetahs-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@headstartwp/core": patch
---

Adding support for basic auth
19 changes: 19 additions & 0 deletions docs/documentation/06-WordPress Integration/basic-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
slug: /wordpress-integration/basic-auth
---

# Basic Auth

If WordPress is protected by Basic Auth (which is common during development) you can tell HeadstartWP the basic auth creds so that all
REST API requests include them. To do so, simply add the following env variables:

```
WP_BASIC_AUTH_USERNAME=username
WP_BASIC_AUTH_PASSWORD=password
```

:::caution
The above env variables will only be accessible server-side and therefore any client-side requests made directly to WordPress will fail. This happens because Next.js only includes env variables prefixed with `NEXT_PUBLIC_` in the browser bundle.

If you want your client-side requests to work, prefix the above variables with `NEXT_PUBLIC_`. But note that the basic auth creds will be leaked to the public.
:::caution
6 changes: 0 additions & 6 deletions packages/core/src/data/api/fetch-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ export const apiGet = async (
args: { [index: string]: any } = {},
burstCache = false,
) => {
const headers = getAuthHeader();

if (headers) {
args.headers = headers;
}

const queryArgs = burstCache
? {
cacheTime: new Date().getTime(),
Expand Down
29 changes: 27 additions & 2 deletions packages/core/src/data/strategies/AbstractFetchStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,27 @@ export abstract class AbstractFetchStrategy<E, Params extends EndpointParams, R
};
}

getAuthHeader(options: Partial<FetchOptions> = {}) {
let bearerAuthHeader = '';
if (options.bearerToken) {
bearerAuthHeader = `Bearer ${options.bearerToken}`;
}

const basicAuthUsername =
process.env.WP_BASIC_AUTH_USERNAME ?? process.env.NEXT_PUBLIC_WP_BASIC_AUTH_USERNAME;
const basicAuthPassword =
process.env.WP_BASIC_AUTH_PASSWORD ?? process.env.NEXT_PUBLIC_WP_BASIC_AUTH_PASSWORD;

if (basicAuthUsername && basicAuthPassword) {
const basicAuth = `Basic ${btoa(`${basicAuthUsername}:${basicAuthPassword}`)}`;
if (bearerAuthHeader) {
return `${basicAuth}, ${bearerAuthHeader}`;
}
return basicAuth;
}
return bearerAuthHeader;
}

/**
* The default fetcher function
*
Expand All @@ -245,9 +266,13 @@ export abstract class AbstractFetchStrategy<E, Params extends EndpointParams, R
const { burstCache = false } = options;

const args = {};
if (options.bearerToken) {

const authHeader = this.getAuthHeader(options);
if (authHeader) {
// @ts-expect-error
args.headers = { Authorization: `Bearer ${options.bearerToken}` };
args.headers = {
Authorization: authHeader,
};
}

const result = await apiGet(`${this.baseURL}${url}`, args, burstCache);
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/data/strategies/SinglePostFetchStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@
/**
* Returns only the post that matches the current path
*
* @param result

Check warning on line 162 in packages/core/src/data/strategies/SinglePostFetchStrategy.ts

View workflow job for this annotation

GitHub Actions / eslint (16.x)

Missing JSDoc @param "result" description
* @param params

Check warning on line 163 in packages/core/src/data/strategies/SinglePostFetchStrategy.ts

View workflow job for this annotation

GitHub Actions / eslint (16.x)

Missing JSDoc @param "params" description
* @returns
*/
getPostThatMatchesCurrentPath(result: T[], params: Partial<P>): T | undefined {
Expand Down Expand Up @@ -198,8 +198,8 @@
/**
* Prepares the post response
*
* @param response

Check warning on line 201 in packages/core/src/data/strategies/SinglePostFetchStrategy.ts

View workflow job for this annotation

GitHub Actions / eslint (16.x)

Missing JSDoc @param "response" description
* @param params

Check warning on line 202 in packages/core/src/data/strategies/SinglePostFetchStrategy.ts

View workflow job for this annotation

GitHub Actions / eslint (16.x)

Missing JSDoc @param "params" description
* @returns
*/
prepareResponse(response: FetchResponse<T[] | T>, params: Partial<P>): FetchResponse<T> {
Expand Down Expand Up @@ -279,14 +279,16 @@
options.bearerToken = params.authToken;
}

const authHeader = this.getAuthHeader(options);

let error;
if (params.revision && params.id) {
try {
const response = await apiGet(
`${this.baseURL}${this.getEndpoint()}/revisions?per_page=1`,
{
headers: {
Authorization: `Bearer ${options.bearerToken}`,
Authorization: authHeader,
},
},
burstCache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,4 +681,66 @@ describe('SinglePostFetchStrategy', () => {
result: {},
});
});

it('handles basic auth', async () => {
const samplePost = { title: 'test', id: 1, link: '/2021/10/post-name' };
const sampleHeaders = {
'x-wp-totalpages': 1,
'x-wp-total': 1,
};

apiGetMock.mockResolvedValue({
headers: sampleHeaders,
json: [samplePost],
});

process.env.WP_BASIC_AUTH_PASSWORD = 'test';
process.env.WP_BASIC_AUTH_USERNAME = 'admin';

const params = fetchStrategy.getParamsFromURL('/2021/10/post-name');
await fetchStrategy.fetcher(fetchStrategy.buildEndpointURL(params), params);

expect(apiGetMock).toHaveBeenNthCalledWith(
1,
'/wp-json/wp/v2/posts?slug=post-name',
{
headers: {
Authorization: 'Basic YWRtaW46dGVzdA==',
},
},
false,
);
});

it('handles basic auth and bearer token', async () => {
const samplePost = { title: 'test', id: 1, link: '/2021/10/post-name' };
const sampleHeaders = {
'x-wp-totalpages': 1,
'x-wp-total': 1,
};

apiGetMock.mockResolvedValue({
headers: sampleHeaders,
json: [samplePost],
});

process.env.WP_BASIC_AUTH_PASSWORD = 'test';
process.env.WP_BASIC_AUTH_USERNAME = 'admin';

const params = fetchStrategy.getParamsFromURL('/2021/10/post-name');
await fetchStrategy.fetcher(fetchStrategy.buildEndpointURL(params), params, {
bearerToken: 'bearer token',
});

expect(apiGetMock).toHaveBeenNthCalledWith(
1,
'/wp-json/wp/v2/posts?slug=post-name',
{
headers: {
Authorization: 'Basic YWRtaW46dGVzdA==, Bearer bearer token',
},
},
false,
);
});
});
Loading