Skip to content

Commit

Permalink
✨ TikTok provider
Browse files Browse the repository at this point in the history
  • Loading branch information
codemasher committed Apr 17, 2024
1 parent 8b67cd7 commit f11a8c9
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .config/.env_example
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@ STRIPE_SECRET=
STRIPE_CALLBACK_URL=
#STRIPE_TESTUSER=

# https://developers.tiktok.com/apps/
TIKTOK_KEY=
TIKTOK_SECRET=
TIKTOK_CALLBACK_URL=
#TIKTOK_TESTUSER=

# https://www.tumblr.com/oauth/apps
TUMBLR_KEY=
TUMBLR_SECRET=
Expand Down
21 changes: 21 additions & 0 deletions examples/get-token/TikTok.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* @link https://developers.tiktok.com/doc/login-kit-web/
*
* @created 11.04.2024
* @author Smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*/
declare(strict_types=1);

use chillerlan\OAuth\Providers\TikTok;

require_once __DIR__.'/../provider-example-common.php';

/** @var \OAuthExampleProviderFactory $factory */
$provider = $factory->getProvider(TikTok::class);

require_once __DIR__.'/_flow-oauth2.php';

exit;
103 changes: 103 additions & 0 deletions src/Providers/TikTok.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
/**
* Class TikTok
*
* @created 11.04.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*
* @noinspection PhpUnused
*/

namespace chillerlan\OAuth\Providers;

use chillerlan\OAuth\Core\{CSRFToken, OAuth2Provider, PKCE, TokenRefresh};
use function array_merge, implode;

/**
* @see https://developers.tiktok.com/doc/login-kit-web/
* @see https://developers.tiktok.com/doc/oauth-user-access-token-management/
*/
class TikTok extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh{

public const IDENTIFIER = 'TIKTOK';

public const SCOPE_VIDEO_UPLOAD = 'video.upload';
public const SCOPE_VIDEO_LIST = 'video.list';
public const SCOPE_VIDEO_PUBLISH = 'video.publish';
public const SCOPE_USER_INFO_BASIC = 'user.info.basic';
public const SCOPE_USER_INFO_PROFILE = 'user.info.profile';
public const SCOPE_USER_INFO_STATS = 'user.info.stats';
public const SCOPE_PORTABILITY_PPOSTPROFILE_ONGOING = 'portability.postsandprofile.ongoing';
public const SCOPE_PORTABILITY_PPOSTPROFILE_SINGLE = 'portability.postsandprofile.single';
public const SCOPE_PORTABILITY_ALL_ONGOING = 'portability.all.ongoing';
public const SCOPE_PORTABILITY_ALL_SINGLE = 'portability.all.single';
public const SCOPE_PORTABILITY_DIRECTMESSAGES_ONGOING = 'portability.directmessages.ongoing';
public const SCOPE_PORTABILITY_DIRECTMESSAGES_SINGLE = 'portability.directmessages.single';
public const SCOPE_PORTABILITY_ACTIVITY_ONGOING = 'portability.activity.ongoing';
public const SCOPE_PORTABILITY_ACTIVITY_SINGLE = 'portability.activity.single';

public const DEFAULT_SCOPES = [
self::SCOPE_USER_INFO_BASIC,
];

protected string $authorizationURL = 'https://www.tiktok.com/v2/auth/authorize/';
protected string $accessTokenURL = 'https://open.tiktokapis.com/v2/oauth/token/';
protected string $revokeURL = 'https://open.tiktokapis.com/v2/oauth/revoke/';
protected string $apiURL = 'https://open.tiktokapis.com';
protected string|null $apiDocs = 'https://developers.tiktok.com/doc/overview/';
protected string|null $applicationURL = 'https://developers.tiktok.com/apps/';
protected string|null $userRevokeURL = 'https://example.com/user/settings/connections';

/**
* @inheritDoc
*/
protected function getAuthorizationURLRequestParams(array $params, array $scopes):array{

unset($params['client_secret']);

$params = array_merge($params, [
'client_key' => $this->options->key,
'redirect_uri' => $this->options->callbackURL,
'response_type' => 'code',
]);

if(!empty($scopes)){
$params['scope'] = implode($this::SCOPES_DELIMITER, $scopes);
}

$params = $this->setCodeChallenge($params, PKCE::CHALLENGE_METHOD_S256);

return $this->setState($params);
}

/**
* @inheritDoc
*/
protected function getAccessTokenRequestBodyParams(string $code):array{

$params = [
'client_key' => $this->options->key,
'client_secret' => $this->options->secret,
'code' => $code,
'grant_type' => 'authorization_code',
'redirect_uri' => $this->options->callbackURL,
];

return $this->setCodeVerifier($params);
}

/**
* @inheritDoc
*/
protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken):array{
return [
'client_key' => $this->options->key,
'client_secret' => $this->options->secret,
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
];
}

}
80 changes: 80 additions & 0 deletions tests/Providers/Unit/TikTokTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/**
* Class TikTokTest
*
* @created 18.04.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\OAuthTest\Providers\Unit;

use chillerlan\HTTP\Utils\QueryUtil;
use chillerlan\OAuth\Providers\TikTok;
use function implode;

/**
* @property \chillerlan\OAuth\Providers\TikTok $provider
*/
final class TikTokTest extends OAuth2ProviderUnitTestAbstract{

protected function getProviderFQCN():string{
return TikTok::class;
}

public function testGetAuthURL():void{
$uri = $this->provider->getAuthorizationURL();
$params = QueryUtil::parse($uri->getQuery());

$this::assertSame($this->getReflectionProperty('authorizationURL'), (string)$uri->withQuery(''));

$this::assertArrayHasKey('client_key', $params);
$this::assertArrayHasKey('redirect_uri', $params);
$this::assertArrayHasKey('response_type', $params);

$this::assertArrayHasKey('state', $params);
}

public function testGetAuthURLRequestParams():void{
$extraparams = ['response_type' => 'whatever', 'foo' => 'bar'];
$scopes = ['scope1', 'scope2', 'scope3'];

$params = $this->invokeReflectionMethod('getAuthorizationURLRequestParams', [$extraparams, $scopes]);

$this::assertSame($this->options->key, $params['client_key']);
$this::assertSame($this->options->callbackURL, $params['redirect_uri']);
$this::assertSame('code', $params['response_type']);
$this::assertSame(implode($this->provider::SCOPES_DELIMITER, $scopes), $params['scope']);
$this::assertSame('bar', $params['foo']);
}

public function testGetAccessTokenRequestBodyParams():void{
$verifier = $this->provider->generateVerifier($this->options->pkceVerifierLength);

$this->storage->storeCodeVerifier($verifier, $this->provider->name);

$params = $this->invokeReflectionMethod('getAccessTokenRequestBodyParams', ['*test_code*']);

$this::assertSame('*test_code*', $params['code']);
$this::assertSame($this->options->callbackURL, $params['redirect_uri']);
$this::assertSame('authorization_code', $params['grant_type']);
$this::assertSame($this->options->key, $params['client_key']);
$this::assertSame($this->options->secret, $params['client_secret']);

$this::assertSame($verifier, $params['code_verifier']);

}

public function testGetRefreshAccessTokenRequestBodyParams():void{
$params = $this->invokeReflectionMethod('getRefreshAccessTokenRequestBodyParams', ['*refresh_token*']);

$this::assertSame('*refresh_token*', $params['refresh_token']);
$this::assertSame($this->options->key, $params['client_key']);
$this::assertSame($this->options->secret, $params['client_secret']);
$this::assertSame('refresh_token', $params['grant_type']);
}


}

0 comments on commit f11a8c9

Please sign in to comment.