Skip to content

Commit

Permalink
modify SessionToken to work with from Checkout UI Extension
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroendelau committed Jul 24, 2024
1 parent 8b76c1d commit 9b59acd
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 8 deletions.
33 changes: 33 additions & 0 deletions src/Objects/Enums/SessionTokenSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Osiset\ShopifyApp\Objects\Enums;

use Funeralzone\ValueObjects\Enums\EnumTrait;
use Funeralzone\ValueObjects\ValueObject;

/**
* API call method types.
*
* @method static ApiMethod GET()
* @method static ApiMethod POST()
* @method static ApiMethod PUT()
* @method static ApiMethod DELETE()
*/
final class SessionTokenSource implements ValueObject
{
use EnumTrait;

/**
* Token form Shopify App
*
* @var int
*/
public const APP = 0;

/**
* Token from UI extension
*
* @var int
*/
public const CHECKOUT_EXTENSION = 1;
}
50 changes: 42 additions & 8 deletions src/Objects/Values/SessionToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use Assert\AssertionFailedException;
use Funeralzone\ValueObjects\Scalars\StringTrait;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Osiset\ShopifyApp\Contracts\Objects\Values\SessionToken as SessionTokenValue;
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopDomain as ShopDomainValue;
use Osiset\ShopifyApp\Objects\Enums\SessionTokenSource;
use Osiset\ShopifyApp\Util;

/**
Expand Down Expand Up @@ -129,6 +131,13 @@ final class SessionToken implements SessionTokenValue
*/
protected $shopDomain;

/**
* Shopify has multiple session tokens, slightly differing in format.
*
* @var string
*/
protected $tokenSource = SessionTokenSource::APP;

/**
* Constructor.
*
Expand Down Expand Up @@ -167,32 +176,38 @@ protected function decodeToken(): void
$this->parts = explode('.', $this->string);
$body = json_decode(Util::base64UrlDecode($this->parts[1]), true);

$this->tokenSource = $this->determineTokenSource($body);

// Confirm token is not malformed
Assert::thatAll([
$body['iss'],
$body['dest'],
$body['aud'],
$body['sub'],
$body['exp'],
$body['nbf'],
$body['iat'],
$body['jti'],
$body['sid'],
... $this->tokenSource === SessionTokenSource::APP
? [
$body['iss'],
$body['sub'],
$body['sid'],
]
: [],
])->notNull(self::EXCEPTION_MALFORMED);

// Format the values
$this->iss = $body['iss'];
$this->iss = $body['iss'] ?? '';
$this->dest = $body['dest'];
$this->aud = $body['aud'];
$this->sub = $body['sub'];
$this->sub = $body['sub'] ?? '';
$this->jti = $body['jti'];
$this->sid = SessionId::fromNative($body['sid']);
$this->sid = SessionId::fromNative($body['sid'] ?? '');
$this->exp = new Carbon($body['exp']);
$this->nbf = new Carbon($body['nbf']);
$this->iat = new Carbon($body['iat']);

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

Expand Down Expand Up @@ -357,7 +372,10 @@ protected function verifySignature(): void
*/
protected function verifyValidity(): void
{
Assert::that($this->iss)->contains($this->dest, self::EXCEPTION_INVALID);
if($this->tokenSource === SessionTokenSource::APP) {
Assert::that($this->iss)->contains($this->dest, self::EXCEPTION_INVALID);
}

Assert::that($this->aud)->eq(Util::getShopifyConfig('api_key', $this->getShopDomain()), self::EXCEPTION_INVALID);
}

Expand All @@ -377,4 +395,20 @@ protected function verifyExpiration(): void
$now->lessThan($this->getLeewayIssuedAt()),
])->false(self::EXCEPTION_EXPIRED);
}

protected function determineTokenSource(array $body): int
{
if(!isset($body['iss']) && !isset($body['sid'])) {
return SessionTokenSource::CHECKOUT_EXTENSION;
}

return SessionTokenSource::APP;
}

protected function findHost(string $destination): ?string
{
return Str::startsWith($destination, 'https')
? parse_url($destination, PHP_URL_HOST)
: $destination;
}
}
40 changes: 40 additions & 0 deletions tests/Objects/Values/SessionTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,49 @@
use Osiset\ShopifyApp\Contracts\Objects\Values\ShopDomain as ShopDomainValue;
use Osiset\ShopifyApp\Objects\Values\SessionToken;
use Osiset\ShopifyApp\Test\TestCase;
use Osiset\ShopifyApp\Util;

class SessionTokenTest extends TestCase
{
public function testShouldProcessForValidCheckoutExtensionToken(): void
{
$now = Carbon::now()->unix();
$this->tokenDefaults = [
'dest' => 'shop-name.myshopify.com',
'aud' => Util::getShopifyConfig('api_key'),
'exp' => $now + 60,
'nbf' => $now,
'iat' => $now,
'jti' => '00000000-0000-0000-0000-000000000000',
];

$token = $this->buildToken();
$st = SessionToken::fromNative($token);

$this->assertInstanceOf(ShopDomainValue::class, $st->getShopDomain());
$this->assertTrue(Str::contains($this->tokenDefaults['dest'], $st->getShopDomain()->toNative()));

$this->assertInstanceOf(Carbon::class, $st->getExpiration());
$this->assertSame($this->tokenDefaults['exp'], $st->getExpiration()->unix());

$this->assertInstanceOf(Carbon::class, $st->getIssuedAt());
$this->assertSame($this->tokenDefaults['iat'], $st->getIssuedAt()->unix());

$this->assertInstanceOf(Carbon::class, $st->getNotBefore());
$this->assertSame($this->tokenDefaults['nbf'], $st->getNotBefore()->unix());

$this->assertSame($this->tokenDefaults['dest'], $st->getDestination());
$this->assertSame($this->tokenDefaults['aud'], $st->getAudience());
$this->assertSame($this->tokenDefaults['jti'], $st->getTokenId());

$this->assertInstanceOf(SessionIdValue::class, $st->getSessionId());
$this->assertSame('', $st->getSessionId()->toNative());

$this->assertSame('', $st->getIssuer());
$this->assertSame('', $st->getSubject());

}

public function testShouldProcessForValidToken(): void
{
$token = $this->buildToken();
Expand Down

0 comments on commit 9b59acd

Please sign in to comment.