Skip to content

Commit

Permalink
Merl 1749 faust should warn if the secret key is invalid (#1777)
Browse files Browse the repository at this point in the history
* Added validation check for disparate FAUST_SECRET_KEYs
* Refactored getWpSecret to const secretWp
* Added unit testing
---------
Co-authored-by: Blake Wilson <[email protected]>
Co-authored-by: John Parris <[email protected]>
  • Loading branch information
TeresaGobble authored Feb 15, 2024
1 parent 572b253 commit 47f6bd0
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 15 deletions.
6 changes: 6 additions & 0 deletions .changeset/honest-buckets-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@faustwp/cli": patch
"@faustwp/wordpress-plugin": patch
---

Faust now warns you if the secret key in your environment is invalid or incorrect.
43 changes: 41 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/faustwp-cli/src/healthCheck/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { verifyGraphQLEndpoint } from './verifyGraphQLEndpoint.js';
* Ensure that everything Faust requires to run is available.
*/
export async function healthCheck(): Promise<void> {
// Check Faust Env varibles before continuing.
validateFaustEnvVars();
// Check Faust Env variables before continuing.
await validateFaustEnvVars();

// Perform our health checks.
await verifyGraphQLEndpoint();
Expand Down
36 changes: 29 additions & 7 deletions packages/faustwp-cli/src/healthCheck/validateFaustEnvVars.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getWpSecret } from '../utils/index.js';
import { getWpSecret, getWpUrl } from '../utils/index.js';
import { errorLog, infoLog, warnLog } from '../stdout/index.js';

export function isWPEngineComSubdomain(url: string) {
Expand All @@ -10,7 +10,9 @@ export function isWPEngineComSubdomain(url: string) {
/**
* Validates that the appropriate Faust related environment variables are set.
*/
export const validateFaustEnvVars = () => {
export const validateFaustEnvVars = async () => {
const secretWp = getWpSecret();

if (!process.env.NEXT_PUBLIC_WORDPRESS_URL) {
errorLog('Could not find NEXT_PUBLIC_WORDPRESS_URL environment variable.');

Expand All @@ -28,15 +30,12 @@ export const validateFaustEnvVars = () => {
);
}

if (!getWpSecret()) {
if (!secretWp) {
warnLog('Could not find FAUST_SECRET_KEY environment variable.');
warnLog('Some functionality may be limited.');
}

if (
process.env.NEXT_PUBLIC_WORDPRESS_URL.startsWith('http://') &&
getWpSecret()
) {
if (process.env.NEXT_PUBLIC_WORDPRESS_URL.startsWith('http://') && secretWp) {
warnLog('Your WordPress site is not running on https!');
warnLog(
'This is a security concern as all traffic with your secret key is in plain text.',
Expand All @@ -45,4 +44,27 @@ export const validateFaustEnvVars = () => {
'Please make sure your production Faust app runs with a WordPress instance on https!',
);
}

if (secretWp) {
// send secret key
const apiUrl = `${getWpUrl()}/?rest_route=/faustwp/v1/validate_secret_key`;
const headers = {
'x-faustwp-secret': secretWp,
};
try {
const response = await fetch(apiUrl, {
headers,
method: 'POST',
});
if (response.status === 401) {
// Unauthorized: User receives a 401 status code AND the message below
errorLog(
'Ensure your FAUST_SECRET_KEY environment variable matches your Secret Key in the Faust WordPress plugin settings',
);
process.exit(1);
}
} catch (error) {
console.log('error', error);
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`healthCheck/validateFaustEnvVars logs an error when the secret key validation fails: Ensure your FAUST_SECRET_KEY environment variable matches your Secret Key in the Faust WordPress plugin settings 1`] = `Promise {}`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
isWPEngineComSubdomain,
validateFaustEnvVars,
} from '../../src/healthCheck/validateFaustEnvVars';
import fetchMock from 'fetch-mock';

/**
* @jest-environment jsdom
*/
Expand All @@ -20,7 +22,7 @@ describe('healthCheck/validateFaustEnvVars', () => {
process.env = envBackup;
});

it('exits with a 1 exit code when the WordPress URL is undefined', () => {
it('exits with a 1 exit code when the WordPress URL is undefined', async () => {
// @ts-ignore
const mockExit = jest.spyOn(process, 'exit').mockImplementation((code) => {
if (code && code !== 0) {
Expand All @@ -30,15 +32,15 @@ describe('healthCheck/validateFaustEnvVars', () => {

// Use try/catch block to mock process.exit
try {
validateFaustEnvVars();
await validateFaustEnvVars();
} catch (err) {
console.log(err);
}

expect(mockExit).toHaveBeenCalledWith(1);
});

it('does not exit or throw an error when the WordPress URL is set', () => {
it('does not exit or throw an error when the WordPress URL is set', async () => {
// @ts-ignore
const mockExit = jest.spyOn(process, 'exit').mockImplementation((code) => {
if (code && code !== 0) {
Expand All @@ -48,10 +50,25 @@ describe('healthCheck/validateFaustEnvVars', () => {

process.env.NEXT_PUBLIC_WORDPRESS_URL = 'http://headless.local';

validateFaustEnvVars();
await validateFaustEnvVars();

expect(mockExit).toBeCalledTimes(0);
});

it('logs an error when the secret key validation fails', async () => {

process.env.NEXT_PUBLIC_WORDPRESS_URL = 'https://headless.local';
process.env.FAUST_SECRET_KEY = 'invalid-secret-key';

fetchMock.post('https://headless.local/wp-json/faustwp/v1/validate_secret_key', {
status: 401,
});

await validateFaustEnvVars();

return expect(Promise.resolve(validateFaustEnvVars())).toMatchSnapshot(`Ensure your FAUST_SECRET_KEY environment variable matches your Secret Key in the Faust WordPress plugin settings`);
});

});

describe('isWPEngineComTLD', () => {
Expand Down
47 changes: 47 additions & 0 deletions plugins/faustwp/includes/rest/callbacks.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ function register_rest_routes() {
)
);

register_rest_route(
'faustwp/v1',
'/validate_secret_key',
array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\\handle_rest_validate_secret_key_callback',
'permission_callback' => __NAMESPACE__ . '\\rest_validate_secret_key_permission_callback',
)
);

/**
* Faust.js packages now use `faustwp/v1/authorize`.
*
Expand Down Expand Up @@ -333,6 +343,8 @@ function rest_process_telemetry_permission_callback( \WP_REST_Request $request )
return rest_authorize_permission_callback( $request );
}



/**
* Callback for WordPress register_rest_route() 'callback' parameter.
*
Expand Down Expand Up @@ -476,3 +488,38 @@ function handle_rest_telemetry_decision_callback( \WP_REST_Request $request ) {
);
return rest_ensure_response( $response );
}

/**
* Callback for WordPress register_rest_route() 'callback' parameter.
*
* Handle POST /faustwp/v1/validate_secret_key response.
*
* @link https://developer.wordpress.org/reference/functions/register_rest_route/
* @link https://developer.wordpress.org/rest-api/extending-the-rest-api/routes-and-endpoints/#endpoint-callback
*
* @param \WP_REST_Request $request Current \WP_REST_Request object.
*
* @return mixed A \WP_REST_Response, or \WP_Error.
*/
function handle_rest_validate_secret_key_callback( \WP_REST_Request $request ) {
return new \WP_REST_Response(
esc_html__( 'Secret key validated!', 'faustwp' ),
200
);
}

/**
* Callback to check permissions for requests to `faustwp/v1/validate_secret_key`.
*
* Authorized if the 'secret_key' settings value and http header 'x-faustwp-secret' match.
*
* @link https://developer.wordpress.org/reference/functions/register_rest_route/
* @link https://developer.wordpress.org/rest-api/extending-the-rest-api/routes-and-endpoints/#permissions-callback
*
* @param \WP_REST_Request $request The current \WP_REST_Request object.
*
* @return bool True if current user can, false if else.
*/
function rest_validate_secret_key_permission_callback( \WP_REST_Request $request ) {
return rest_authorize_permission_callback( $request );
}

0 comments on commit 47f6bd0

Please sign in to comment.