|
3 | 3 | namespace Osiset\ShopifyApp\Http\Middleware;
|
4 | 4 |
|
5 | 5 | use Closure;
|
6 |
| -use Exception; |
7 | 6 | use Illuminate\Http\Request;
|
8 |
| -use Osiset\ShopifyApp\Contracts\ShopModel as IShopModel; |
| 7 | +use Illuminate\Support\Facades\{Cache, Log, Redirect}; |
| 8 | +use Osiset\ShopifyApp\Contracts\ShopModel; |
9 | 9 | use Osiset\ShopifyApp\Util;
|
10 | 10 |
|
11 | 11 | class VerifyScopes
|
12 | 12 | {
|
| 13 | + private const CURRENT_SCOPES_CACHE_KEY = 'currentScopes'; |
| 14 | + |
13 | 15 | /**
|
14 |
| - * Checks if a shop has all required access scopes. |
15 |
| - * If a required access scope is missing, it will redirect the app |
16 |
| - * for re-authentication |
17 |
| - * |
18 |
| - * @param Request $request The request object. |
19 |
| - * @param Closure $next The next action. |
20 |
| - * |
21 |
| - * @throws Exception |
| 16 | + * Handle an incoming request. |
22 | 17 | *
|
23 |
| - * @return mixed |
| 18 | + * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse |
24 | 19 | */
|
25 | 20 | public function handle(Request $request, Closure $next)
|
26 | 21 | {
|
27 |
| - /** @var $shop IShopModel */ |
| 22 | + /** @var ?ShopModel */ |
28 | 23 | $shop = auth()->user();
|
29 |
| - $scopesResponse = $shop->api()->rest('GET', '/admin/oauth/access_scopes.json'); |
30 |
| - if ($scopesResponse && $scopesResponse['errors']) { |
31 |
| - return $next($request); |
32 |
| - } |
33 |
| - $scopes = json_decode(json_encode($scopesResponse['body']['access_scopes']), false); |
34 |
| - $scopes = array_map(static function ($scope) { |
35 |
| - return $scope->handle; |
36 |
| - }, $scopes); |
37 |
| - |
38 |
| - $requiredScopes = explode(',', Util::getShopifyConfig('api_scopes')); |
39 |
| - $missingScopes = array_diff($requiredScopes, $scopes); |
40 |
| - if (count($missingScopes) === 0) { |
41 |
| - return $next($request); |
| 24 | + |
| 25 | + if ($shop) { |
| 26 | + $response = $this->currentScopes($shop); |
| 27 | + |
| 28 | + if ($response['hasErrors']) { |
| 29 | + return $next($request); |
| 30 | + } |
| 31 | + |
| 32 | + $hasMissingScopes = filled( |
| 33 | + array_diff( |
| 34 | + explode(',', config('shopify-app.api_scopes')), |
| 35 | + $response['result'] |
| 36 | + ) |
| 37 | + ); |
| 38 | + |
| 39 | + if ($hasMissingScopes) { |
| 40 | + Cache::forget($this->cacheKey($shop->getDomain()->toNative())); |
| 41 | + |
| 42 | + return Redirect::route(Util::getShopifyConfig('route_names.authenticate'), [ |
| 43 | + 'shop' => $shop->getDomain()->toNative(), |
| 44 | + 'host' => $request->get('host'), |
| 45 | + 'locale' => $request->get('locale'), |
| 46 | + ]); |
| 47 | + } |
42 | 48 | }
|
43 | 49 |
|
44 |
| - return redirect()->route( |
45 |
| - Util::getShopifyConfig('route_names.authenticate'), |
46 |
| - [ |
47 |
| - 'shop' => $shop->getDomain()->toNative(), |
48 |
| - 'host' => $request->get('host'), |
49 |
| - 'locale' => $request->get('locale'), |
50 |
| - ] |
| 50 | + return $next($request); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * @return array{hasErrors: bool, result: string[]} |
| 55 | + */ |
| 56 | + private function currentScopes(ShopModel $shop): array |
| 57 | + { |
| 58 | + /** @var array{errors: bool, status: int, body: \Gnikyt\BasicShopifyAPI\ResponseAccess} */ |
| 59 | + $response = Cache::remember( |
| 60 | + $this->cacheKey($shop->getDomain()->toNative()), |
| 61 | + now()->addDay(), |
| 62 | + fn() => $shop->api()->graph('{ |
| 63 | + currentAppInstallation { |
| 64 | + accessScopes { |
| 65 | + handle |
| 66 | + } |
| 67 | + } |
| 68 | + }') |
51 | 69 | );
|
| 70 | + |
| 71 | + if (! $response['errors'] && blank(data_get($response['body']->toArray(), 'data.currentAppInstallation.userErrors'))) { |
| 72 | + return [ |
| 73 | + 'hasErrors' => false, |
| 74 | + 'result' => array_column( |
| 75 | + data_get($response['body'], 'data.currentAppInstallation.accessScopes')->toArray(), |
| 76 | + 'handle' |
| 77 | + ), |
| 78 | + ]; |
| 79 | + } |
| 80 | + |
| 81 | + Log::error('Fetch current app installation access scopes error: ' . json_encode(data_get($response['body']->toArray(), 'data.currentAppInstallation.userErrors'))); |
| 82 | + |
| 83 | + return [ |
| 84 | + 'hasErrors' => true, |
| 85 | + 'result' => [], |
| 86 | + ]; |
| 87 | + } |
| 88 | + |
| 89 | + private function cacheKey(string $shopDomain): string |
| 90 | + { |
| 91 | + return sprintf("{$shopDomain}.%s", self::CURRENT_SCOPES_CACHE_KEY); |
52 | 92 | }
|
53 | 93 | }
|
0 commit comments