mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Add Kick OAuth adapter
This commit is contained in:
@@ -200,6 +200,17 @@ return [
|
||||
'mock' => false,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\Google',
|
||||
],
|
||||
'kick' => [
|
||||
'name' => 'Kick',
|
||||
'developers' => 'https://docs.kick.com/',
|
||||
'icon' => 'icon-kick',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\Kick',
|
||||
],
|
||||
'linkedin' => [
|
||||
'name' => 'LinkedIn',
|
||||
'developers' => 'https://developer.linkedin.com/',
|
||||
|
||||
@@ -123,6 +123,7 @@ use Appwrite\Utopia\Response\Model\OAuth2Figma;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2GitHub;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Gitlab;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Google;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Kick;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Linkedin;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Notion;
|
||||
use Appwrite\Utopia\Response\Model\OAuth2Oidc;
|
||||
@@ -421,6 +422,7 @@ Response::setModel(new OAuth2Authentik());
|
||||
Response::setModel(new OAuth2Auth0());
|
||||
Response::setModel(new OAuth2Oidc());
|
||||
Response::setModel(new OAuth2Okta());
|
||||
Response::setModel(new OAuth2Kick());
|
||||
Response::setModel(new OAuth2Apple());
|
||||
Response::setModel(new PolicyPasswordDictionary());
|
||||
Response::setModel(new PolicyPasswordHistory());
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://docs.kick.com/getting-started/generating-tokens-oauth2-flow
|
||||
// https://docs.kick.com/getting-started/scopes
|
||||
|
||||
class Kick extends OAuth2
|
||||
{
|
||||
private const PKCE_STATE_KEY = '_pkce';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'user:read',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $pkceVerifier = '';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'kick';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
$state = $this->state;
|
||||
$state[self::PKCE_STATE_KEY] = $this->getPKCEVerifier();
|
||||
|
||||
return 'https://id.kick.com/oauth/authorize?' . \http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->appID,
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'state' => \json_encode($state),
|
||||
'code_challenge' => $this->getPKCEChallenge(),
|
||||
'code_challenge_method' => 'S256',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://id.kick.com/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'redirect_uri' => $this->callback,
|
||||
'code_verifier' => $this->getPKCEVerifier(),
|
||||
'code' => $code,
|
||||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$headers = ['Content-Type: application/x-www-form-urlencoded'];
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
'https://id.kick.com/oauth/token',
|
||||
$headers,
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'refresh_token' => $refreshToken,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return isset($user['user_id']) ? (string)$user['user_id'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified.
|
||||
*
|
||||
* Kick only returns an email when the user has granted the `user:read`
|
||||
* scope and the account email is verified, so a non-empty email is
|
||||
* treated as verified.
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
return !empty($this->getUserEmail($accessToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['name'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$headers = ['Authorization: Bearer ' . $accessToken];
|
||||
$response = \json_decode($this->request(
|
||||
'GET',
|
||||
'https://api.kick.com/public/v1/users',
|
||||
$headers
|
||||
), true);
|
||||
|
||||
$this->user = $response['data'][0] ?? [];
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the PKCE verifier from the state on the callback so the same
|
||||
* value generated in getLoginURL() can be sent to the token endpoint.
|
||||
*
|
||||
* @param string $state
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function parseState(string $state): ?array
|
||||
{
|
||||
$parsed = \json_decode($state, true);
|
||||
|
||||
if (!\is_array($parsed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$verifier = $parsed[self::PKCE_STATE_KEY] ?? null;
|
||||
if (\is_string($verifier)) {
|
||||
$this->pkceVerifier = $verifier;
|
||||
}
|
||||
|
||||
unset($parsed[self::PKCE_STATE_KEY]);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
private function getPKCEVerifier(): string
|
||||
{
|
||||
if ($this->pkceVerifier === '') {
|
||||
$this->pkceVerifier = \rtrim(\strtr(\base64_encode(\random_bytes(64)), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
return $this->pkceVerifier;
|
||||
}
|
||||
|
||||
private function getPKCEChallenge(): string
|
||||
{
|
||||
return \rtrim(\strtr(\base64_encode(\hash('sha256', $this->getPKCEVerifier(), true)), '+/', '-_'), '=');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Kick;
|
||||
|
||||
use Appwrite\Auth\OAuth2\Kick;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class Update extends Base
|
||||
{
|
||||
public static function getProviderId(): string
|
||||
{
|
||||
return 'kick';
|
||||
}
|
||||
|
||||
public static function getProviderClass(): string
|
||||
{
|
||||
return Kick::class;
|
||||
}
|
||||
|
||||
public static function getProviderLabel(): string
|
||||
{
|
||||
return 'Kick';
|
||||
}
|
||||
|
||||
public static function getProviderSDKMethod(): string
|
||||
{
|
||||
return 'updateOAuth2Kick';
|
||||
}
|
||||
|
||||
public static function getResponseModel(): string
|
||||
{
|
||||
return Response::MODEL_OAUTH2_KICK;
|
||||
}
|
||||
|
||||
public static function getClientIdDescription(): string
|
||||
{
|
||||
return '\'Client ID\' of Kick OAuth2 app. For example: 01KQ7C00000000000001MFHS32';
|
||||
}
|
||||
|
||||
public static function getClientSecretDescription(): string
|
||||
{
|
||||
return '\'Client Secret\' of Kick OAuth2 app. For example: 34ac5600000000000000000000000000000000000000000000000000e830c8b';
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Figma\Update as Update
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\GitHub\Update as UpdateOAuth2GitHub;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Gitlab\Update as UpdateOAuth2Gitlab;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Google\Update as UpdateOAuth2Google;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Kick\Update as UpdateOAuth2Kick;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Linkedin\Update as UpdateOAuth2Linkedin;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Notion\Update as UpdateOAuth2Notion;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Oidc\Update as UpdateOAuth2Oidc;
|
||||
@@ -205,5 +206,6 @@ class Http extends Service
|
||||
$this->addAction(UpdateOAuth2Auth0::getName(), new UpdateOAuth2Auth0());
|
||||
$this->addAction(UpdateOAuth2Oidc::getName(), new UpdateOAuth2Oidc());
|
||||
$this->addAction(UpdateOAuth2Okta::getName(), new UpdateOAuth2Okta());
|
||||
$this->addAction(UpdateOAuth2Kick::getName(), new UpdateOAuth2Kick());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ class Response extends SwooleResponse
|
||||
public const MODEL_OAUTH2_OIDC = 'oAuth2Oidc';
|
||||
public const MODEL_OAUTH2_APPLE = 'oAuth2Apple';
|
||||
public const MODEL_OAUTH2_OKTA = 'oAuth2Okta';
|
||||
public const MODEL_OAUTH2_KICK = 'oAuth2Kick';
|
||||
|
||||
// Health
|
||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class OAuth2Kick extends OAuth2Base
|
||||
{
|
||||
public function getProviderLabel(): string
|
||||
{
|
||||
return 'Kick';
|
||||
}
|
||||
|
||||
public function getClientIdExample(): string
|
||||
{
|
||||
return '01KQ7C00000000000001MFHS32';
|
||||
}
|
||||
|
||||
public function getClientSecretExample(): string
|
||||
{
|
||||
return '34ac5600000000000000000000000000000000000000000000000000e830c8b';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'OAuth2Kick';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_OAUTH2_KICK;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user