| 1 |
<?php |
| 2 |
/** |
| 3 |
* Auth API class |
| 4 |
* |
| 5 |
* Handles authentication related REST API endpoints + the admin-post handler |
| 6 |
* that receives the inbound POST form from the billing portal popup. |
| 7 |
* |
| 8 |
* @package SureCookie\Inc\Modules\Auth |
| 9 |
* @since 0.0.1-beta.3 |
| 10 |
*/ |
| 11 |
|
| 12 |
namespace SureCookie\Inc\Modules\Auth; |
| 13 |
|
| 14 |
use SureCookie\Inc\API\Base; |
| 15 |
use SureCookie\Inc\Functions\SendJson; |
| 16 |
use SureCookie\Inc\Traits\GetInstance; |
| 17 |
use WP_REST_Request; |
| 18 |
use WP_REST_Server; |
| 19 |
|
| 20 |
if ( ! defined( 'ABSPATH' ) ) { |
| 21 |
exit; // Exit if accessed directly. |
| 22 |
} |
| 23 |
|
| 24 |
/** |
| 25 |
* Class Api |
| 26 |
* |
| 27 |
* Handles authentication related REST API endpoints. |
| 28 |
*/ |
| 29 |
class Api extends Base { |
| 30 |
use GetInstance; |
| 31 |
|
| 32 |
/** |
| 33 |
* Admin-post.php action slug for the inbound auth callback (issue #466). |
| 34 |
*/ |
| 35 |
public const CALLBACK_ACTION = 'surecookie_auth_callback'; |
| 36 |
|
| 37 |
/** |
| 38 |
* Register REST routes. |
| 39 |
* |
| 40 |
* @since 0.0.1-beta.3 |
| 41 |
* @return void |
| 42 |
*/ |
| 43 |
public function register_routes(): void { |
| 44 |
register_rest_route( |
| 45 |
$this->get_api_namespace(), |
| 46 |
'/auth', |
| 47 |
[ |
| 48 |
'methods' => WP_REST_Server::READABLE, |
| 49 |
'callback' => [ $this, 'get_auth_payload' ], |
| 50 |
'permission_callback' => [ $this, 'validate_permission' ], |
| 51 |
] |
| 52 |
); |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* GET /surecookie/v1/auth - returns the form payload the frontend POSTs |
| 57 |
* to the billing portal (issue #466). Replaces the URL-string response. |
| 58 |
* |
| 59 |
* @since 0.0.1-beta.3 |
| 60 |
* @param WP_REST_Request<array<string, mixed>> $request Request object. |
| 61 |
* @return void |
| 62 |
*/ |
| 63 |
public function get_auth_payload( $request ): void { |
| 64 |
$controller = Controller::get_instance(); |
| 65 |
|
| 66 |
if ( $controller->get_auth_status() ) { |
| 67 |
SendJson::success( |
| 68 |
[ |
| 69 |
'is_authenticated' => true, |
| 70 |
'email' => $controller->get_auth_email(), |
| 71 |
'message' => __( 'Authentication is already completed.', 'surecookie' ), |
| 72 |
] |
| 73 |
); |
| 74 |
return; |
| 75 |
} |
| 76 |
|
| 77 |
$payload = $controller->get_auth_payload(); |
| 78 |
|
| 79 |
if ( is_wp_error( $payload ) ) { |
| 80 |
SendJson::error( [ 'message' => $payload->get_error_message() ] ); |
| 81 |
return; |
| 82 |
} |
| 83 |
|
| 84 |
SendJson::success( $payload ); |
| 85 |
} |
| 86 |
|
| 87 |
/** |
| 88 |
* Handle the inbound POST form from the billing portal popup (issue #466). |
| 89 |
* |
| 90 |
* Receives `{action, flow_id, access_key}` as form fields, runs save_auth, |
| 91 |
* and redirects the popup to /wp-admin/admin.php?page=surecookie with a |
| 92 |
* benign `?auth=success|fail` query - the ciphertext never lands in the |
| 93 |
* post-OAuth URL. |
| 94 |
* |
| 95 |
* @since 0.0.1-beta.3 |
| 96 |
*/ |
| 97 |
public function handle_inbound_callback(): void { |
| 98 |
// Cast to string up front - array-typed $_POST values would trigger |
| 99 |
// sanitize_text_field warnings. flow_id is then validated as UUID v4 |
| 100 |
// inside save_auth(); access_key is base64-URL-safe ciphertext that |
| 101 |
// sanitization would corrupt, validated by decrypt. |
| 102 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Replay protection is via one-time transient in save_auth. flow_id validated as UUID v4 downstream. |
| 103 |
$flow_raw = isset( $_POST['flow_id'] ) && is_string( $_POST['flow_id'] ) ? wp_unslash( $_POST['flow_id'] ) : ''; |
| 104 |
$flow_id = sanitize_text_field( (string) $flow_raw ); |
| 105 |
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- ciphertext is base64 URL-safe; sanitization would corrupt it. Validated by save_auth. |
| 106 |
$access_key = isset( $_POST['access_key'] ) && is_string( $_POST['access_key'] ) ? (string) wp_unslash( $_POST['access_key'] ) : ''; |
| 107 |
|
| 108 |
$redirect_base = admin_url( 'admin.php?page=surecookie' ); |
| 109 |
|
| 110 |
if ( $access_key === '' || $flow_id === '' ) { |
| 111 |
wp_safe_redirect( add_query_arg( 'auth', 'fail', $redirect_base ) ); |
| 112 |
exit; |
| 113 |
} |
| 114 |
|
| 115 |
$result = Controller::get_instance()->save_auth( $access_key, $flow_id ); |
| 116 |
|
| 117 |
$status = is_wp_error( $result ) ? 'fail' : 'success'; |
| 118 |
wp_safe_redirect( add_query_arg( 'auth', $status, $redirect_base ) ); |
| 119 |
exit; |
| 120 |
} |
| 121 |
} |
| 122 |
|