From 4369c04f535a4afdc61b39c292cca18cfdc6f411 Mon Sep 17 00:00:00 2001 From: Graham Sutton Date: Tue, 5 Sep 2023 12:22:12 -0400 Subject: [PATCH 1/4] #466 - Upgrade Laravel to use version 10 (latest). --- .github/workflows/ci.yml | 1 - README.md | 2 +- web/app/Http/Kernel.php | 2 +- web/app/Http/Middleware/TrustProxies.php | 2 +- web/composer.json | 37 ++++++++++++------------ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2040b3f45..a534c6f3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: fail-fast: false matrix: php-version: - - "8.0" - "8.1" - "8.2" defaults: diff --git a/README.md b/README.md index c4a5a6b51..58e620ebe 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Open the URL generated in your console. Once you grant permission to the app, yo ### Application Storage -This template uses [Laravel's Eloquent framework](https://laravel.com/docs/9.x/eloquent) to store Shopify session data. +This template uses [Laravel's Eloquent framework](https://laravel.com/docs/10.x/eloquent) to store Shopify session data. It provides migrations to create the necessary tables in your database, and it stores and loads session data from them. The database that works best for you depends on the data your app needs and how it is queried. diff --git a/web/app/Http/Kernel.php b/web/app/Http/Kernel.php index 3eb42388d..707187c48 100644 --- a/web/app/Http/Kernel.php +++ b/web/app/Http/Kernel.php @@ -16,7 +16,7 @@ class Kernel extends HttpKernel protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, - \Fruitcake\Cors\HandleCors::class, + \Illuminate\Http\Middleware\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, diff --git a/web/app/Http/Middleware/TrustProxies.php b/web/app/Http/Middleware/TrustProxies.php index a297111e7..c0617abea 100644 --- a/web/app/Http/Middleware/TrustProxies.php +++ b/web/app/Http/Middleware/TrustProxies.php @@ -2,7 +2,7 @@ namespace App\Http\Middleware; -use Fideloper\Proxy\TrustProxies as Middleware; +use Illuminate\Http\Middleware\TrustProxies as Middleware; use Illuminate\Http\Request; class TrustProxies extends Middleware diff --git a/web/composer.json b/web/composer.json index dc6382c2d..6182013e4 100644 --- a/web/composer.json +++ b/web/composer.json @@ -2,31 +2,28 @@ "name": "laravel/laravel", "type": "project", "description": "The Laravel Framework.", - "keywords": [ - "framework", - "laravel" - ], + "keywords": ["framework", "laravel"], "license": "UNLICENSED", "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.1.0 || ~8.2.0", "ext-xml": "*", "ext-zip": "*", - "doctrine/dbal": "^3.1", - "fideloper/proxy": "^4.4", - "fruitcake/laravel-cors": "^3.0", - "guzzlehttp/guzzle": "^7.0.1", - "laravel/framework": "^8.12", - "laravel/tinker": "^2.5", + "doctrine/dbal": "^3.0", + "guzzlehttp/guzzle": "^7.2", + "laravel/framework": "^10.0", + "laravel/sanctum": "^3.2", + "laravel/tinker": "^2.8", "shopify/shopify-api": "^5.0", "squizlabs/php_codesniffer": "^3.6" }, "require-dev": { - "facade/ignition": "^2.5", "fakerphp/faker": "^1.9.1", - "laravel/sail": "^1.0.1", - "mockery/mockery": "^1.4.2", - "nunomaduro/collision": "^5.0", - "phpunit/phpunit": "^9.3.3" + "laravel/pint": "^1.0", + "laravel/sail": "^1.18", + "mockery/mockery": "^1.4.4", + "nunomaduro/collision": "^7.0", + "phpunit/phpunit": "^10.1", + "spatie/laravel-ignition": "^2.0" }, "autoload": { "psr-4": { @@ -68,8 +65,12 @@ "config": { "optimize-autoloader": true, "preferred-install": "dist", - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "php-http/discovery": true + } }, - "minimum-stability": "dev", + "minimum-stability": "stable", "prefer-stable": true } From 72caa21e59fc5b70137b2ce44c697f69380d1560 Mon Sep 17 00:00:00 2001 From: Graham Sutton Date: Tue, 5 Sep 2023 12:39:48 -0400 Subject: [PATCH 2/4] #466 - Update Session model to implement Authenticatable and enable "web" auth guard to use custom ShopifyGuard implementation for authenticating sessions. --- web/app/Lib/ShopifyGuard.php | 94 +++++++++++++++++++++++ web/app/Models/Session.php | 44 ++++++++++- web/app/Providers/AuthServiceProvider.php | 7 +- web/config/auth.php | 64 +++------------ 4 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 web/app/Lib/ShopifyGuard.php diff --git a/web/app/Lib/ShopifyGuard.php b/web/app/Lib/ShopifyGuard.php new file mode 100644 index 000000000..077c1a610 --- /dev/null +++ b/web/app/Lib/ShopifyGuard.php @@ -0,0 +1,94 @@ +provider = $provider; + } + + /** + * Get the current Shopify session. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ + public function user() + { + if (! is_null($this->session)) { + return $this->session; + } + + $request = request(); + + if (! $request->hasHeader('Authorization')) { + return; + } + + $shopifySession = Utils::loadCurrentSession( + $request->header(), + $request->cookie(), + false + ); + + if (is_null($shopifySession)) { + return; + } + + $this->session = $this->provider->retrieveById($shopifySession->getId()); + + return $this->session; + } + + /** + * Validate a user's credentials. + * + * Credentials are validated through Shopify sessions so we can bypass the + * need to validate. + * + * @param array $credentials + * @return bool + */ + public function validate(array $credentials = []) + { + return true; + } + + /** + * Via remember is disabled for Shopify sessions. + * + * @return bool + */ + public function viaRemember() + { + return false; + } +} diff --git a/web/app/Models/Session.php b/web/app/Models/Session.php index 4962fc8ad..8b0643d18 100644 --- a/web/app/Models/Session.php +++ b/web/app/Models/Session.php @@ -3,9 +3,49 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; +use Illuminate\Foundation\Auth\User as Authenticatable; -class Session extends Model +class Session extends Authenticatable { use HasFactory; + + /** + * The attributes that should be hidden for arrays. + * + * @var array + */ + protected $hidden = ['access_token']; + + /** + * Disable "remember me" compatibility. + * + * @var null + */ + protected $rememberTokenName = null; + + /** + * The non-primary key value that is used to identify a unique session. + * + * Normally, for a user User model, this would be something like "email", + * however, the equivalent for sessions is the session_id column. + * + * @return string + */ + public function getAuthIdentifierName() + { + return 'session_id'; + } + + /** + * Sessions do not have passwords, they have access tokens instead. + * + * This is the functional equivalent of a password in the context of + * Shopify sessions. + * + * @return string + */ + public function getAuthPassword() + { + return 'access_token'; + } } diff --git a/web/app/Providers/AuthServiceProvider.php b/web/app/Providers/AuthServiceProvider.php index ce7449164..29a927953 100644 --- a/web/app/Providers/AuthServiceProvider.php +++ b/web/app/Providers/AuthServiceProvider.php @@ -2,7 +2,10 @@ namespace App\Providers; +use App\Lib\ShopifyGuard; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider @@ -25,6 +28,8 @@ public function boot() { $this->registerPolicies(); - // + Auth::extend('shopify', function (Application $app, string $name, array $config) { + return new ShopifyGuard(Auth::createUserProvider($config['provider'])); + }); } } diff --git a/web/config/auth.php b/web/config/auth.php index 365d479c1..b357f4290 100644 --- a/web/config/auth.php +++ b/web/config/auth.php @@ -14,8 +14,7 @@ */ 'defaults' => [ -// 'guard' => 'web', -// 'passwords' => 'users', + 'guard' => 'web', ], /* @@ -36,12 +35,11 @@ */ 'guards' => [ -// 'web' => [ -// 'driver' => 'session', -// 'provider' => 'users', -// ], - - ], + 'web' => [ + 'driver' => 'shopify', + 'provider' => 'sessions', + ], + ], /* |-------------------------------------------------------------------------- @@ -61,52 +59,10 @@ */ 'providers' => [ -// 'users' => [ -// 'driver' => 'eloquent', -// 'model' => App\Models\User::class, -// ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], + 'sessions' => [ + 'driver' => 'eloquent', + 'model' => \App\Models\Session::class, + ], ], - /* - |-------------------------------------------------------------------------- - | Resetting Passwords - |-------------------------------------------------------------------------- - | - | You may specify multiple password reset configurations if you have more - | than one user table or model in the application and you want to have - | separate password reset settings based on the specific user types. - | - | The expire time is the number of minutes that the reset token should be - | considered valid. This security feature keeps tokens short-lived so - | they have less time to be guessed. You may change this as needed. - | - */ - -// 'passwords' => [ -// 'users' => [ -// 'provider' => 'users', -// 'table' => 'password_resets', -// 'expire' => 60, -// 'throttle' => 60, -// ], -// ], - - /* - |-------------------------------------------------------------------------- - | Password Confirmation Timeout - |-------------------------------------------------------------------------- - | - | Here you may define the amount of seconds before a password confirmation - | times out and the user is prompted to re-enter their password via the - | confirmation screen. By default, the timeout lasts for three hours. - | - */ - -// 'password_timeout' => 10800, - ]; From 9aac8ccc3fd6a0de8371cc46987f4436c48dec49 Mon Sep 17 00:00:00 2001 From: Graham Sutton Date: Tue, 5 Sep 2023 13:55:47 -0400 Subject: [PATCH 3/4] #466 - Enable unit and feature testing. --- web/config/services.php | 5 ++++ web/tests/CreatesApplication.php | 22 ++++++++++++++++++ web/tests/TestCase.php | 40 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 web/tests/CreatesApplication.php create mode 100644 web/tests/TestCase.php diff --git a/web/config/services.php b/web/config/services.php index 2a1d616c7..97ba404ef 100644 --- a/web/config/services.php +++ b/web/config/services.php @@ -30,4 +30,9 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], + 'shopify' => [ + 'key' => env('SHOPIFY_API_KEY'), + 'secret' => env('SHOPIFY_API_SECRET'), + ] + ]; diff --git a/web/tests/CreatesApplication.php b/web/tests/CreatesApplication.php new file mode 100644 index 000000000..547152f6a --- /dev/null +++ b/web/tests/CreatesApplication.php @@ -0,0 +1,22 @@ +make(Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/web/tests/TestCase.php b/web/tests/TestCase.php new file mode 100644 index 000000000..b88374e31 --- /dev/null +++ b/web/tests/TestCase.php @@ -0,0 +1,40 @@ +withoutMiddleware([ + EnsureShopifyInstalled::class, + EnsureShopifySession::class + ]); + + $shopifySecret = Config::get('services.shopify.secret'); + + $token = JWT::encode(['dest' => $session->shop], $shopifySecret, 'HS256'); + + return parent::actingAs($session, $guard)->withToken($token); + } +} From d079b1bf7fee5faec247aac82c58ffa73447b9c2 Mon Sep 17 00:00:00 2001 From: Graham Sutton Date: Tue, 5 Sep 2023 14:34:16 -0400 Subject: [PATCH 4/4] #466 - Fixed incorrect docblock variable name in TestCase.php. --- web/tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/tests/TestCase.php b/web/tests/TestCase.php index b88374e31..412278c20 100644 --- a/web/tests/TestCase.php +++ b/web/tests/TestCase.php @@ -19,7 +19,7 @@ abstract class TestCase extends BaseTestCase * Disables Shopify middleware and generates a mock Bearer token to successfully * authenticate against Shopify for testing purposes. * - * @param \Illuminate\Contracts\Auth\Authenticatable $user + * @param \Illuminate\Contracts\Auth\Authenticatable $session * @param string|null $guard * * @return self