Skip to content

Commit 1cc5029

Browse files
authored
modify SessionToken to be compatible with Checkout UI Extension (#329)
* modify SessionToken to work with from Checkout UI Extension * pass linter
1 parent 61ad790 commit 1cc5029

File tree

3 files changed

+112
-8
lines changed

3 files changed

+112
-8
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Osiset\ShopifyApp\Objects\Enums;
4+
5+
use Funeralzone\ValueObjects\Enums\EnumTrait;
6+
use Funeralzone\ValueObjects\ValueObject;
7+
8+
/**
9+
* API call method types.
10+
*
11+
* @method static SessionTokenSource APP()
12+
* @method static SessionTokenSource CHECKOUT_EXTENSION()
13+
*/
14+
final class SessionTokenSource implements ValueObject
15+
{
16+
use EnumTrait;
17+
18+
/**
19+
* Token form Shopify App
20+
*
21+
* @var int
22+
*/
23+
public const APP = 0;
24+
25+
/**
26+
* Token from UI extension
27+
*
28+
* @var int
29+
*/
30+
public const CHECKOUT_EXTENSION = 1;
31+
}

src/Objects/Values/SessionToken.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
use Assert\AssertionFailedException;
77
use Funeralzone\ValueObjects\Scalars\StringTrait;
88
use Illuminate\Support\Carbon;
9+
use Illuminate\Support\Str;
910
use Osiset\ShopifyApp\Contracts\Objects\Values\SessionToken as SessionTokenValue;
1011
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopDomain as ShopDomainValue;
12+
use Osiset\ShopifyApp\Objects\Enums\SessionTokenSource;
1113
use Osiset\ShopifyApp\Util;
1214

1315
/**
@@ -129,6 +131,13 @@ final class SessionToken implements SessionTokenValue
129131
*/
130132
protected $shopDomain;
131133

134+
/**
135+
* Shopify has multiple session tokens, slightly differing in format.
136+
*
137+
* @var string
138+
*/
139+
protected $tokenSource = SessionTokenSource::APP;
140+
132141
/**
133142
* Constructor.
134143
*
@@ -167,32 +176,38 @@ protected function decodeToken(): void
167176
$this->parts = explode('.', $this->string);
168177
$body = json_decode(Util::base64UrlDecode($this->parts[1]), true);
169178

179+
$this->tokenSource = $this->determineTokenSource($body);
180+
170181
// Confirm token is not malformed
171182
Assert::thatAll([
172-
$body['iss'],
173183
$body['dest'],
174184
$body['aud'],
175-
$body['sub'],
176185
$body['exp'],
177186
$body['nbf'],
178187
$body['iat'],
179188
$body['jti'],
180-
$body['sid'],
189+
... $this->tokenSource === SessionTokenSource::APP
190+
? [
191+
$body['iss'],
192+
$body['sub'],
193+
$body['sid'],
194+
]
195+
: [],
181196
])->notNull(self::EXCEPTION_MALFORMED);
182197

183198
// Format the values
184-
$this->iss = $body['iss'];
199+
$this->iss = $body['iss'] ?? '';
185200
$this->dest = $body['dest'];
186201
$this->aud = $body['aud'];
187-
$this->sub = $body['sub'];
202+
$this->sub = $body['sub'] ?? '';
188203
$this->jti = $body['jti'];
189-
$this->sid = SessionId::fromNative($body['sid']);
204+
$this->sid = SessionId::fromNative($body['sid'] ?? '');
190205
$this->exp = new Carbon($body['exp']);
191206
$this->nbf = new Carbon($body['nbf']);
192207
$this->iat = new Carbon($body['iat']);
193208

194209
// Parse the shop domain from the destination
195-
$host = parse_url($body['dest'], PHP_URL_HOST);
210+
$host = $this->findHost($body['dest']);
196211
$this->shopDomain = NullableShopDomain::fromNative($host);
197212
}
198213

@@ -357,7 +372,10 @@ protected function verifySignature(): void
357372
*/
358373
protected function verifyValidity(): void
359374
{
360-
Assert::that($this->iss)->contains($this->dest, self::EXCEPTION_INVALID);
375+
if ($this->tokenSource === SessionTokenSource::APP) {
376+
Assert::that($this->iss)->contains($this->dest, self::EXCEPTION_INVALID);
377+
}
378+
361379
Assert::that($this->aud)->eq(Util::getShopifyConfig('api_key', $this->getShopDomain()), self::EXCEPTION_INVALID);
362380
}
363381

@@ -377,4 +395,20 @@ protected function verifyExpiration(): void
377395
$now->lessThan($this->getLeewayIssuedAt()),
378396
])->false(self::EXCEPTION_EXPIRED);
379397
}
398+
399+
protected function determineTokenSource(array $body): int
400+
{
401+
if (!isset($body['iss']) && !isset($body['sid'])) {
402+
return SessionTokenSource::CHECKOUT_EXTENSION;
403+
}
404+
405+
return SessionTokenSource::APP;
406+
}
407+
408+
protected function findHost(string $destination): ?string
409+
{
410+
return Str::startsWith($destination, 'https')
411+
? parse_url($destination, PHP_URL_HOST)
412+
: $destination;
413+
}
380414
}

tests/Objects/Values/SessionTokenTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,48 @@
99
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopDomain as ShopDomainValue;
1010
use Osiset\ShopifyApp\Objects\Values\SessionToken;
1111
use Osiset\ShopifyApp\Test\TestCase;
12+
use Osiset\ShopifyApp\Util;
1213

1314
class SessionTokenTest extends TestCase
1415
{
16+
public function testShouldProcessForValidCheckoutExtensionToken(): void
17+
{
18+
$now = Carbon::now()->unix();
19+
$this->tokenDefaults = [
20+
'dest' => 'shop-name.myshopify.com',
21+
'aud' => Util::getShopifyConfig('api_key'),
22+
'exp' => $now + 60,
23+
'nbf' => $now,
24+
'iat' => $now,
25+
'jti' => '00000000-0000-0000-0000-000000000000',
26+
];
27+
28+
$token = $this->buildToken();
29+
$st = SessionToken::fromNative($token);
30+
31+
$this->assertInstanceOf(ShopDomainValue::class, $st->getShopDomain());
32+
$this->assertTrue(Str::contains($this->tokenDefaults['dest'], $st->getShopDomain()->toNative()));
33+
34+
$this->assertInstanceOf(Carbon::class, $st->getExpiration());
35+
$this->assertSame($this->tokenDefaults['exp'], $st->getExpiration()->unix());
36+
37+
$this->assertInstanceOf(Carbon::class, $st->getIssuedAt());
38+
$this->assertSame($this->tokenDefaults['iat'], $st->getIssuedAt()->unix());
39+
40+
$this->assertInstanceOf(Carbon::class, $st->getNotBefore());
41+
$this->assertSame($this->tokenDefaults['nbf'], $st->getNotBefore()->unix());
42+
43+
$this->assertSame($this->tokenDefaults['dest'], $st->getDestination());
44+
$this->assertSame($this->tokenDefaults['aud'], $st->getAudience());
45+
$this->assertSame($this->tokenDefaults['jti'], $st->getTokenId());
46+
47+
$this->assertInstanceOf(SessionIdValue::class, $st->getSessionId());
48+
$this->assertSame('', $st->getSessionId()->toNative());
49+
50+
$this->assertSame('', $st->getIssuer());
51+
$this->assertSame('', $st->getSubject());
52+
}
53+
1554
public function testShouldProcessForValidToken(): void
1655
{
1756
$token = $this->buildToken();

0 commit comments

Comments
 (0)