| @@ -7,10 +7,12 @@ | ||
| 7 | 7 | if (!defined('ABSPATH')) { |
| 8 | 8 | exit; |
| 9 | 9 | } |
| 10 | 10 | |
| 11 | +use Metricool\Support\Helpers\Storages\RequestStorage; | |
| 11 | 12 | use Metricool\Traits\HasRestAccess; |
| 12 | 13 | use Metricool\Services\DashboardService; |
| 14 | +use Metricool\Support\Validation\Validator; | |
| 13 | 15 | use Metricool\Interfaces\FeatureInterface; |
| 14 | 16 | use Metricool\Services\MetricoolAccountService; |
| 15 | 17 | use Metricool\Features\Onboarding\Services\OAuthService; |
| 16 | 18 | use Metricool\Support\Helpers\Storages\EnvironmentConfig; |
| @@ -28,8 +30,9 @@ | ||
| 28 | 30 | private CreateAccountService $accounts; |
| 29 | 31 | private OAuthService $oauth; |
| 30 | 32 | private DashboardService $dashboard; |
| 31 | 33 | private MetricoolAccountService $account; |
| 34 | + private RequestStorage $request; | |
| 32 | 35 | |
| 33 | 36 | public function __construct( |
| 34 | 37 | OnboardingService $onboarding, |
| 35 | 38 | CreateAccountService $accounts, |
| @@ -35,9 +38,10 @@ | ||
| 35 | 38 | CreateAccountService $accounts, |
| 36 | 39 | EnvironmentConfig $env, |
| 37 | 40 | OAuthService $oauth, |
| 38 | 41 | DashboardService $dashboard, |
| 39 | - MetricoolAccountService $account | |
| 42 | + MetricoolAccountService $account, | |
| 43 | + RequestStorage $request | |
| 40 | 44 | ) { |
| 41 | 45 | $this->onboarding = $onboarding; |
| 42 | 46 | $this->accounts = $accounts; |
| 43 | 47 | $this->env = $env; |
| @@ -43,13 +47,17 @@ | ||
| 43 | 47 | $this->env = $env; |
| 44 | 48 | $this->oauth = $oauth; |
| 45 | 49 | $this->dashboard = $dashboard; |
| 46 | 50 | $this->account = $account; |
| 51 | + $this->request = $request; | |
| 47 | 52 | } |
| 48 | 53 | |
| 49 | 54 | public function register(): void |
| 50 | 55 | { |
| 51 | 56 | add_filter('metricool_rest_routes', [$this, 'addRoutes']); |
| 57 | + | |
| 58 | + // Intercept OAuth callback on the dashboard page | |
| 59 | + add_action('load-toplevel_page_metricool', [$this, 'oauthCallback']); | |
| 52 | 60 | } |
| 53 | 61 | |
| 54 | 62 | /** |
| 55 | 63 | * Add onboarding routes to the existing routes of our plugin |
| @@ -67,16 +75,11 @@ | ||
| 67 | 75 | ]; |
| 68 | 76 | |
| 69 | 77 | $routes['onboarding/oauth_redirect'] = [ |
| 70 | 78 | 'methods' => 'GET', |
| 71 | - 'callback' => [$this, 'oauthRedirect'], | |
| 79 | + 'callback' => [$this, 'getOauthRedirectUrl'], | |
| 72 | 80 | ]; |
| 73 | 81 | |
| 74 | - $routes['onboarding/oauth_callback'] = [ | |
| 75 | - 'methods' => 'GET', | |
| 76 | - 'callback' => [$this, 'oauthCallback'], | |
| 77 | - ]; | |
| 78 | - | |
| 79 | 82 | return $routes; |
| 80 | 83 | } |
| 81 | 84 | |
| 82 | 85 | /** |
| @@ -84,26 +87,24 @@ | ||
| 84 | 87 | * automatically. |
| 85 | 88 | */ |
| 86 | 89 | public function createAccount(\WP_REST_Request $request): \WP_REST_Response |
| 87 | 90 | { |
| 88 | - $email = (string) $request->get_param('email'); | |
| 89 | - $password = (string) $request->get_param('password'); | |
| 90 | - $marketing = (bool) $request->get_param('marketing'); | |
| 91 | - $terms = (bool) $request->get_param('terms'); | |
| 92 | - $captcha = (string) $request->get_param('captcha'); | |
| 91 | + $validated = Validator::validate($request->get_params(), [ | |
| 92 | + 'email' => 'required|email', | |
| 93 | + 'password' => 'required|string', | |
| 94 | + 'captcha' => 'required|string', | |
| 95 | + 'terms' => 'accepted', | |
| 96 | + 'marketing' => 'boolean', | |
| 97 | + ]); | |
| 93 | 98 | |
| 94 | - // Validate fields | |
| 95 | - if (!is_email($email) || empty($password) || empty($captcha) || !$terms) { | |
| 96 | - return $this->sendHttpErrorResponse( | |
| 97 | - __('Validation failed.', 'metricool'), | |
| 98 | - [], | |
| 99 | - 422 | |
| 100 | - ); | |
| 101 | - } | |
| 102 | - | |
| 103 | 99 | // Attempt to create the account |
| 104 | 100 | try { |
| 105 | - $this->accounts->createAccount($captcha, $email, $password, $marketing); | |
| 101 | + $this->accounts->createAccount( | |
| 102 | + $validated['captcha'], | |
| 103 | + $validated['email'], | |
| 104 | + $validated['password'], | |
| 105 | + rest_sanitize_boolean($validated['marketing'] ?? false) | |
| 106 | + ); | |
| 106 | 107 | } catch (CreateAccountException $e) { |
| 107 | 108 | return $this->sendHttpErrorResponse($e->getMessage(), ['reason' => $e->getReason()], $e->getCode()); |
| 108 | 109 | } |
| 109 | 110 | |
| @@ -125,9 +126,13 @@ | ||
| 125 | 126 | // Store the blogId if it was provided by the client, to retrieve the necessary blog information |
| 126 | 127 | try { |
| 127 | 128 | $this->onboarding->finalizeOnboarding($blogId); |
| 128 | 129 | } catch (BrandAccessDeniedException $e) { |
| 129 | - return $this->sendHttpErrorResponse(__('Could not retrieve brand. Pick another brand, try again or contact support.', 'metricool'), [], 403); | |
| 130 | + return $this->sendHttpErrorResponse( | |
| 131 | + __('You don’t have sufficient permissions to connect this brand. Please pick another brand or sign in with a different account.', 'metricool'), | |
| 132 | + [], | |
| 133 | + 403 | |
| 134 | + ); | |
| 130 | 135 | } catch (Throwable $e) { |
| 131 | 136 | return $this->sendHttpErrorResponse(wp_kses_post(sprintf( |
| 132 | 137 | /* translators: %1$s is opening link and %2$s is closing link */ |
| 133 | 138 | __('Something went wrong. Please try again or %1$sleave a support message%2$s.', 'metricool'), |
| @@ -141,11 +146,10 @@ | ||
| 141 | 146 | |
| 142 | 147 | /** |
| 143 | 148 | * Build and return the Metricool OAuth authorize URL. |
| 144 | 149 | */ |
| 145 | - public function oauthRedirect(\WP_REST_Request $request): \WP_REST_Response | |
| 150 | + public function getOauthRedirectUrl(\WP_REST_Request $request): \WP_REST_Response | |
| 146 | 151 | { |
| 147 | - // Check if authorizationUrl is a secure URL, otherwise redirect to the dashboard | |
| 148 | 152 | if (is_ssl() === false) { |
| 149 | 153 | return $this->sendHttpErrorResponse(__('HTTPS is required to be able to authorize this website.', 'metricool'), [], 400); |
| 150 | 154 | } |
| 151 | 155 | |
| @@ -157,12 +161,18 @@ | ||
| 157 | 161 | /** |
| 158 | 162 | * Handle the OAuth callback from Metricool. Exchanges the authorization |
| 159 | 163 | * code for tokens, authenticates the user, and redirects to the dashboard. |
| 160 | 164 | */ |
| 161 | - public function oauthCallback(\WP_REST_Request $request): \WP_REST_Response | |
| 165 | + public function oauthCallback(): void | |
| 162 | 166 | { |
| 163 | - $code = (string) $request->get_param('code'); | |
| 164 | - $state = (string) $request->get_param('state'); | |
| 167 | + $action = $this->request->getString('global.metricool_action'); | |
| 168 | + | |
| 169 | + if ($action !== OAuthService::REDIRECT_ACTION) { | |
| 170 | + return; | |
| 171 | + } | |
| 172 | + | |
| 173 | + $code = $this->request->getString('global.code'); | |
| 174 | + $state = $this->request->getString('global.state'); | |
| 165 | 175 | |
| 166 | 176 | try { |
| 167 | 177 | $this->oauth->authenticateWithCode($code, $state); |
| 168 | 178 | } catch (Throwable $e) { |