$state, 'verifier' => $verifier, 'platform_url' => $platform_url, ), self::TTL_SECONDS ); return $platform_url . '/connect/wordpress?' . http_build_query( array( 'return' => $return_url, 'state' => $state, 'challenge' => self::challenge( $verifier ), ), '', '&', PHP_QUERY_RFC3986 ); } /** * Finishes connecting when 404 Emails sends the browser back. * * @param string $state The `state` from the redirect. * @param string|null $code The one-time code, or null when the person cancelled. * @return string One of the RESULT_* constants. */ public static function complete( $state, $code ) { $pending = get_transient( self::transient_key() ); delete_transient( self::transient_key() ); if ( ! is_array( $pending ) || empty( $pending['state'] ) || ! is_string( $state ) || ! hash_equals( (string) $pending['state'], $state ) ) { // Not a connection this admin started here (or it took too long). return self::RESULT_EXPIRED; } if ( null === $code || '' === $code ) { return self::RESULT_CANCELLED; } $response = Api_Client::exchange_connect_code( (string) $pending['platform_url'], (string) $code, (string) $pending['verifier'] ); if ( ! $response->success || empty( $response->data['apiKey'] ) ) { return self::RESULT_FAILED; } // Connecting again (e.g. to give the site a new permission): the old key is no longer needed. if ( Settings::is_connected() ) { Api_Client::from_settings()->revoke_connection(); } Settings::update_platform_url_raw( (string) $pending['platform_url'] ); Settings::set_api_key( (string) $response->data['apiKey'] ); Settings::set_workspace_name( isset( $response->data['workspaceName'] ) ? (string) $response->data['workspaceName'] : '' ); Settings::set_connection_result( Settings::CONNECTION_CONNECTED ); return self::RESULT_CONNECTED; } /** * "Disconnect": revokes this site's key on 404 Emails (best effort) and * forgets it here, keeping every other setting. */ public static function disconnect() { if ( Settings::is_connected() ) { Api_Client::from_settings()->revoke_connection(); } Settings::clear_connection(); } /** base64url(SHA-256(verifier)) - the PKCE S256 challenge. */ public static function challenge( $verifier ) { return rtrim( strtr( base64_encode( hash( 'sha256', $verifier, true ) ), '+/', '-_' ), '=' ); } /** Random URL-safe characters (A-Z a-z 0-9 - _). */ public static function random_token( $length ) { $token = rtrim( strtr( base64_encode( random_bytes( (int) ceil( $length * 3 / 4 ) + 2 ) ), '+/', '-_' ), '=' ); return substr( $token, 0, $length ); } private static function transient_key() { return self::TRANSIENT_PREFIX . get_current_user_id(); } }