Skip to content

Commit

Permalink
Added proxy features
Browse files Browse the repository at this point in the history
  • Loading branch information
drewbaker committed May 24, 2023
1 parent 0cdb2c6 commit 67c5268
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 0 deletions.
139 changes: 139 additions & 0 deletions acf/proxy-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
[
{
"key": "group_6466cc3ca925a",
"title": "Proxy Settings",
"fields": [
{
"key": "field_6466cc3df4887",
"label": "Providers",
"name": "providers",
"aria-label": "",
"type": "repeater",
"instructions": "Enter details for the services you wish to proxy.",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"show_in_graphql": 1,
"layout": "row",
"pagination": 0,
"min": 0,
"max": 0,
"collapsed": "",
"button_label": "Add Provider",
"rows_per_page": 20,
"sub_fields": [
{
"key": "field_6466cc64f4888",
"label": "Name",
"name": "name",
"aria-label": "",
"type": "text",
"instructions": "The name of the provider. Used in frontend code for reference.",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"show_in_graphql": 1,
"default_value": "",
"maxlength": "",
"placeholder": "",
"prepend": "",
"append": "",
"parent_repeater": "field_6466cc3df4887"
},
{
"key": "field_6466cc6af4889",
"label": "Base URL",
"name": "base_url",
"aria-label": "",
"type": "url",
"instructions": "The base URL of the API you are trying to proxy. Tip: leave off the trailing slash.",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"show_in_graphql": 1,
"default_value": "",
"placeholder": "",
"parent_repeater": "field_6466cc3df4887"
},
{
"key": "field_6466cc74f488a",
"label": "Authorization header",
"name": "authorization_header",
"aria-label": "",
"type": "text",
"instructions": "The HTTP Authorization header to add to the request. Often this will be of the format `Bearer xxxxxxxxx`",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"show_in_graphql": 1,
"default_value": "",
"maxlength": "",
"placeholder": "",
"prepend": "",
"append": "",
"parent_repeater": "field_6466cc3df4887"
}
]
},
{
"key": "field_6466ccb3f488b",
"label": "Domain restricted",
"name": "domain_restricted",
"aria-label": "",
"type": "true_false",
"instructions": "When true, only frontend requests from the Site and WordPress URLs will be allowed. When false, requests from all domains will be allowed. This is a security issue, so only have this false during when testing from localhost.",
"required": 0,
"conditional_logic": 0,
"wrapper": {
"width": "",
"class": "",
"id": ""
},
"show_in_graphql": 1,
"message": "Restrict access to the Site Address and WordPress Address",
"default_value": 1,
"ui_on_text": "",
"ui_off_text": "",
"ui": 1
}
],
"location": [
[
{
"param": "options_page",
"operator": "==",
"value": "proxy-settings"
}
]
],
"menu_order": 0,
"position": "normal",
"style": "default",
"label_placement": "top",
"instruction_placement": "label",
"hide_on_screen": "",
"active": true,
"description": "",
"show_in_rest": 0,
"show_in_graphql": 0,
"graphql_field_name": "proxySettings",
"map_graphql_types_from_location_rules": 0,
"graphql_types": ""
}
]
1 change: 1 addition & 0 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
require_once get_template_directory() . '/functions/developer-role.php';
require_once get_template_directory() . '/functions/widgets.php';
require_once get_template_directory() . '/functions/svg.php';
require_once get_template_directory() . '/functions/proxy.php';
12 changes: 12 additions & 0 deletions functions/acf-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ public function add_options() {
'position' => '60.1',
)
);
acf_add_options_page(
array(
'page_title' => 'Proxy Settings',
'menu_title' => 'Proxy Settings',
'menu_slug' => 'proxy-settings',
'capability' => 'activate_plugins',
'redirect' => false,
'show_in_graphql' => false,
'icon_url' => 'dashicons-privacy',
'position' => '80.1'
)
);
}

/**
Expand Down
188 changes: 188 additions & 0 deletions functions/proxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php
/**
* This file handles the built in proxy that the frontend can use to bypass cors
*
* @package fuxt-backend
*/

/*
* Register custom Proxy API endpoints
*/
function fuxt_proxy_init()
{
// Setup new endpoint
register_rest_route("fuxt/v1", "/proxy", [
// Create Subscriber
[
"methods" => "POST, GET, DELETE, PATCH, PUT",
"callback" => "fuxt_proxy_do_request",
"permission_callback" => "fuxt_proxy_check_permission",
],
]);

// Change headers to allow the custom headers we need
add_filter(
"rest_pre_serve_request",
"fuxt_proxy_add_custom_cors_headers",
20,
4
);
}
add_action("rest_api_init", "fuxt_proxy_init");

/*
* Check's that the request matches allowed Origin and Provider name is in ACF repeater field
* Returns true || WP_Error
*/
function fuxt_proxy_check_permission($request)
{
// Security is be on, so check that Origin is on whitelist
$domain_restricted = get_field("domain_restricted", "option");
if (
$domain_restricted &&
!fuxt_proxy_request_from_allowed_origin($request)
) {
return new WP_Error(
"permission",
"Your origin is not allowed to make this Proxy request"
);
}

// Passed Origin checks, now check requested asked for an approved Provider
$proxy_name = $request->get_header("Fuxt-Proxy-Name");
$provider = fuxt_proxy_get_provider("name", $proxy_name);

// Allow if Provider is found
if ($proxy_name && $provider) {
return true;
}

return new WP_Error("permission", "Your requested Proxy Name is invalid");
}

/*
* Forwards request to allowed API, adds in Bearer token from ACF Proxy Settings field group
* Returns WP_REST_Response || WP_Error
*/
function fuxt_proxy_do_request($request)
{
// Get bearer token from ACF field, include in request below
$proxy_name = $request->get_header("Fuxt-Proxy-Name");
$proxy_endpoint = $request->get_header("Fuxt-Proxy-Endpoint");
$provider = fuxt_proxy_get_provider("name", $proxy_name);

// Get any found tokens from ACF, build out full request URL
$auth_header = $provider["authorization_header"] ?? "";
$url = $provider["base_url"] . $proxy_endpoint;

// Encode the body to JSON if supplied
$body = $request->get_json_params();
if ($body && is_array($body)) {
$body = json_encode($body);
}

// Setup HTTP Request, pass through as much settings as we can
$args = [
"headers" => [
"Authorization" => $auth_header,
"Content-Type" => $request->get_header("Content-Type"),
],
"method" => $request->get_method(),
"body" => $body ?? "",
];

// Send remote request! Go Proxy Go!
$response = wp_remote_request($url, $args);

// Retrieve information from the $response
$response_code = wp_remote_retrieve_response_code($response);
$response_message = wp_remote_retrieve_response_message($response);
$response_headers = wp_remote_retrieve_headers($response);
$response_body = wp_remote_retrieve_body($response);

// Check if response is JSON, if so then decode it so that when we send it later it's not double encoded
if( fuxt_proxy_is_json($response_body) ) {
$response_body = json_decode($response_body);
}

// Return data or error back to frontend
if (!is_wp_error($response)) {
return new WP_REST_Response($response_body, $response_code, (array)$response_headers);
}

return new WP_Error($response_code, $response_message, $response_body);
}

/*
* This customizes the CORS headers the server will accept, allowing use of our Proxy custom headers
* Returns true || false
*/
function fuxt_proxy_add_custom_cors_headers($served, $result, $request, $server)
{
// Abort if not a request to the Proxy endpoint
if ($request->get_route() !== "/fuxt/v1/proxy") {
return $served;
}

// Now add our headers
header(
"Access-Control-Allow-Headers: Fuxt-Proxy-Name, Fuxt-Proxy-Endpoint, Content-Type, Authorization"
);

return $served;
}

/*
* Check that the request is from an allowed Origin.
* Returns true || false
*/
function fuxt_proxy_request_from_allowed_origin($request)
{
$origin = get_http_origin();

// Start with site url as allowed origin.
$allowed_origins = [site_url(), home_url()];

// Add fuxt home url to allowed origin.
$fuxt_home_url = get_option("fuxt_home_url");
if ($fuxt_home_url) {
$allowed_origins[] = $fuxt_home_url;
}

$allowed_origins = apply_filters("fuxt_allowed_origins", $allowed_origins);

// Current request comes from an Origin that is allowed
if (in_array($origin, $allowed_origins, true)) {
return true;
}

return false;
}

/*
* Return the Provider if found, or false.
* Returns Array || false
*/
function fuxt_proxy_get_provider($search_key, $search_value)
{
$proxy_providers = get_field("providers", "option");

$columns = array_column($proxy_providers, $search_key);
$found = array_search($search_value, $columns);

// Return found Provider
if (is_int($found)) {
return $proxy_providers[$found];
}

return false;
}

/*
* Checks if a string is JSON
* Returns Array || false
*/
function fuxt_proxy_is_json($str) {
$json = json_decode($str);
return $json && $str != $json;
}

0 comments on commit 67c5268

Please sign in to comment.