forrest icon indicating copy to clipboard operation
forrest copied to clipboard

Reusing the access/refresh tokens

Open vladan-me opened this issue 2 years ago • 2 comments

Hi,

I'm confused about reusing the tokens, the package seems to support only one-time use communication? Here's the flow for multi-user connections:

  • Let the user connect using WebServer authentication method
  • Store the access/refresh tokens for the future use
  • Synchronize the contacts/leads etc. on a daily basis

The tokens can be stored in the session, cache, or object, all of those are short-term storage as I understand. Moreover, even if I store the tokens myself after Forrest::callback(); I still can't reuse them because there's no method that supports such functionality? Am I missing something fundamental?

Thanks, Vladan

vladan-me avatar Apr 15 '22 06:04 vladan-me

I'm struggling with this as well.

rowan-christmas avatar May 10 '22 05:05 rowan-christmas

So I've found that the the forrest.php cache file is being weirdly ignored. If I change to a new sfdc.php config then things work as expected.

rowan-christmas avatar May 16 '22 00:05 rowan-christmas

I've recently seen this as well. @omniphx any insights?

jreeter avatar Feb 10 '23 22:02 jreeter

I've not yet run into an issue with caching, but related to reusing tokens this is the solution that ended up working for my team.

We extended ForrestServiceProvider.php from this package and made our own CustomStorage class that implemented StorageInterface. This allowed us to handle saving our data to the related Eloquent model.

<?php

namespace App\Providers;

use App\Models\User;
use App\Utilities\Integrations\Salesforce\Forrest\CustomStorage;
use App\Utilities\Integrations\Salesforce\Forrest\CustomWebServer;
use Omniphx\Forrest\Formatters\JSONFormatter;
use Omniphx\Forrest\Providers\Laravel\ForrestServiceProvider;
use Omniphx\Forrest\Providers\Laravel\LaravelEncryptor;
use Omniphx\Forrest\Providers\Laravel\LaravelEvent;
use Omniphx\Forrest\Providers\Laravel\LaravelInput;
use Omniphx\Forrest\Repositories\InstanceURLRepository;
use Omniphx\Forrest\Repositories\RefreshTokenRepository;
use Omniphx\Forrest\Repositories\ResourceRepository;
use Omniphx\Forrest\Repositories\StateRepository;
use Omniphx\Forrest\Repositories\TokenRepository;
use Omniphx\Forrest\Repositories\VersionRepository;

class CustomForrestServiceProvider extends ForrestServiceProvider
{
    public function register()
    {
        $authenticationType = config('forrest.authentication');
        if ($authenticationType === 'WebServer' || empty($authenticationType)) {
            $this->app->bind('forrest', function ($app, $parameters) {
                $user = $parameters['user'] ?? \Auth::user();

                // Config options
                $settings           = config('forrest');

                // Dependencies
                $httpClient    = $this->getClient();
                $input     = new LaravelInput(app('request'));
                $event     = new LaravelEvent(app('events'));
                $encryptor = new LaravelEncryptor(app('encrypter'));
                $redirect  = $this->getRedirect();
                $settingsStorage = new CustomStorage($user->team, 'settings');
                $connectionSettingStorage = new CustomStorage($user->team, 'connection_settings');

                $refreshTokenRepo = new RefreshTokenRepository($encryptor, $connectionSettingStorage);
                $tokenRepo        = new TokenRepository($encryptor, $connectionSettingStorage);
                $resourceRepo     = new ResourceRepository($settingsStorage);
                $versionRepo      = new VersionRepository($settingsStorage);
                $instanceURLRepo  = new InstanceURLRepository($tokenRepo, $settings);
                $stateRepo        = new StateRepository($settingsStorage);

                $formatter = new JSONFormatter($tokenRepo, $settings);

                return new CustomWebServer($httpClient,
                    $encryptor,
                    $event,
                    $input,
                    $redirect,
                    $instanceURLRepo,
                    $refreshTokenRepo,
                    $resourceRepo,
                    $stateRepo,
                    $tokenRepo,
                    $versionRepo,
                    $formatter,
                    $settings);
                });
        } else {
            parent::register();
        }
    }
}

Here's what the CustomStorage class looks like in our case:

<?php

namespace App\Utilities\Integrations\Salesforce\Forrest;

use App\Models\Team;
use Omniphx\Forrest\Exceptions\MissingKeyException;
use Omniphx\Forrest\Interfaces\StorageInterface;

class CustomStorage implements StorageInterface
{
    /**
     * @var Team $team
     */
    protected $team;

    /**
     * @var string $field
     */
    protected $field;

    public function __construct(Team $team, string $field = 'settings')
    {
        $this->team = $team;
        $this->field = $field;
    }

    public function put($key, $value)
    {
        $model = $this->model();
        $field = $model->{$this->field};
        $field[$key] = $value;
        $model->{$this->field} = $field;
        $model->save();
    }

    public function get($key)
    {
        if(!$this->has($key)) {
            throw new MissingKeyException(sprintf('No value for requested key: %s', $key));
        }

        return $this->model()->{$this->field}[$key];
    }

    public function has($key)
    {
        return array_key_exists($key, $this->model()->{$this->field});
    }

    /**
     * @return Integration
     */
    private function model()
    {
        return  Model::firstOrCreate([
           ....
        ], [
           ....
        ]);
    }
}

SimonChaw avatar Feb 13 '23 10:02 SimonChaw

@SimonChaw @vladan-me -- added the ability (v2.16.0) to pass in a custom storage class without needing to completely replace the service provider. To implement, just update your config:

'storage' => [
    'type' => App\Storage\CustomStorage::class,
],

If the config is not being recognized, it's possible there is a conflict with other packages see: https://github.com/omniphx/forrest/issues/262

omniphx avatar Feb 26 '23 22:02 omniphx