diff --git a/CHANGELOG.md b/CHANGELOG.md index d873f5d..ba5aa77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Change Skin](https://wiki.vg/Mojang_API#Change_Skin) endpoint. - [Reset Skin](https://wiki.vg/Mojang_API#Reset_Skin) endpoint. - [Blocked Servers](https://wiki.vg/Mojang_API#Blocked_Servers) endpoint. +- [Refresh](https://wiki.vg/Authentication#Refresh) endpoint. +- [Signout](https://wiki.vg/Authentication#Signout) endpoint. +- [Invalidate](https://wiki.vg/Authentication#Invalidate) endpoint. ### Changed - The constructor no longer has arguments. ### Fixed - Change `static` to `self` in `\Ely\Mojang\Response\Properties\Factory` to allow its extending. +- Fix `validate` endpoint URL. ### Removed - `\Ely\Mojang\Api::create()` static method. Use constructor instead. diff --git a/src/Api.php b/src/Api.php index 2b4b920..55c3eda 100644 --- a/src/Api.php +++ b/src/Api.php @@ -304,6 +304,35 @@ class Api { ); } + /** + * @param string $accessToken + * @param string $clientToken + * + * @return \Ely\Mojang\Response\AuthenticateResponse + * + * @throws \GuzzleHttp\Exception\GuzzleException + * + * @url https://wiki.vg/Authentication#Refresh + */ + public function refresh(string $accessToken, string $clientToken): Response\AuthenticateResponse { + $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/refresh', [ + 'json' => [ + 'accessToken' => $accessToken, + 'clientToken' => $clientToken, + 'requestUser' => true, + ], + ]); + $body = $this->decode($response->getBody()->getContents()); + + return new Response\AuthenticateResponse( + $body['accessToken'], + $body['clientToken'], + [], + $body['selectedProfile'], + $body['user'] + ); + } + /** * @param string $accessToken * @@ -315,7 +344,7 @@ class Api { */ public function validate(string $accessToken): bool { try { - $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/authenticate', [ + $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/validate', [ 'json' => [ 'accessToken' => $accessToken, ], @@ -330,6 +359,51 @@ class Api { return false; } + /** + * @param string $accessToken + * @param string $clientToken + * + * @throws GuzzleException + * + * @url https://wiki.vg/Authentication#Invalidate + */ + public function invalidate(string $accessToken, string $clientToken): void { + $this->getClient()->request('POST', 'https://authserver.mojang.com/invalidate', [ + 'json' => [ + 'accessToken' => $accessToken, + 'clientToken' => $clientToken, + ], + ]); + } + + /** + * @param string $login + * @param string $password + * + * @return bool + * + * @throws GuzzleException + * + * @url https://wiki.vg/Authentication#Signout + */ + public function signout(string $login, string $password): bool { + try { + $response = $this->getClient()->request('POST', 'https://authserver.mojang.com/signout', [ + 'json' => [ + 'username' => $login, + 'password' => $password, + ], + ]); + if ($response->getStatusCode() === 204) { + return true; + } + } catch (Exception\ForbiddenException $e) { + // Suppress exception and let it just exit below + } + + return false; + } + /** * @param string $accessToken * @param string $accountUuid diff --git a/tests/ApiTest.php b/tests/ApiTest.php index a0e4d16..3b23e23 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Ely\Mojang\Test; use Ely\Mojang\Api; +use Ely\Mojang\Exception\ForbiddenException; use Ely\Mojang\Exception\NoContentException; use Ely\Mojang\Middleware\ResponseConverterMiddleware; use Ely\Mojang\Middleware\RetryMiddleware; @@ -410,14 +411,120 @@ class ApiTest extends TestCase { $this->assertRegExp('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $body['clientToken']); } + public function testAuthenticateInvalid() { + $this->mockHandler->append(new Response(403)); + $this->expectException(ForbiddenException::class); + $this->api->authenticate('MockUsername', 'some password'); + } + + public function testRefreshSuccessful() { + $this->mockHandler->append($this->createResponse(200, [ + 'accessToken' => 'new access token value', + 'clientToken' => 'client token value', + 'selectedProfile' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'name' => 'MockUsername', + ], + 'user' => [ + 'id' => '86f6e3695b764412a29820cac1d4d0d6', + 'properties' => [ + [ + 'name' => 'preferredLanguage', + 'value' => 'en', + ], + [ + 'name' => 'twitch_access_token', + 'value' => 'twitch oauth token', + ], + ], + ], + ])); + + $result = $this->api->refresh('access token value', 'client token value'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('https://authserver.mojang.com/refresh', (string)$request->getUri()); + + $this->assertSame('new access token value', $result->getAccessToken()); + $this->assertSame('client token value', $result->getClientToken()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getSelectedProfile()->getId()); + $this->assertSame('MockUsername', $result->getSelectedProfile()->getName()); + $this->assertFalse($result->getSelectedProfile()->isLegacy()); + $this->assertFalse($result->getSelectedProfile()->isDemo()); + + $this->assertSame('86f6e3695b764412a29820cac1d4d0d6', $result->getUser()->getId()); + + $this->assertSame('preferredLanguage', $result->getUser()->getProperties()[0]->getName()); + $this->assertSame('en', $result->getUser()->getProperties()[0]->getValue()); + $this->assertSame('twitch_access_token', $result->getUser()->getProperties()[1]->getName()); + $this->assertSame('twitch oauth token', $result->getUser()->getProperties()[1]->getValue()); + } + + public function testRefreshInvalid() { + $this->mockHandler->append(new Response(403)); + $this->expectException(ForbiddenException::class); + $this->api->refresh('access token value', 'client token value'); + } + public function testValidateSuccessful() { $this->mockHandler->append(new Response(204)); - $this->assertTrue($this->api->validate('mocked access token')); + + $result = $this->api->validate('mocked access token'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('https://authserver.mojang.com/validate', (string)$request->getUri()); + + $this->assertTrue($result); } public function testValidateInvalid() { $this->mockHandler->append(new Response(403)); - $this->assertFalse($this->api->validate('mocked access token')); + + $result = $this->api->validate('mocked access token'); + + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $this->assertSame('https://authserver.mojang.com/validate', (string)$request->getUri()); + + $this->assertFalse($result); + } + + public function testInvalidateSuccessful() { + $this->mockHandler->append(new Response(200)); + $this->api->invalidate('mocked access token', 'mocked client token'); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + $params = json_decode($request->getBody()->getContents(), true); + + $this->assertSame('https://authserver.mojang.com/invalidate', (string)$request->getUri()); + + $this->assertSame('mocked access token', $params['accessToken']); + $this->assertSame('mocked client token', $params['clientToken']); + } + + public function testSignoutSuccessful() { + $this->mockHandler->append(new Response(204)); + $result = $this->api->signout('MockUsername', 'some password'); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + + $this->assertSame('https://authserver.mojang.com/signout', (string)$request->getUri()); + + $this->assertTrue($result); + } + + public function testSignoutInvalid() { + $this->mockHandler->append(new Response(403)); + $result = $this->api->signout('MockUsername', 'some password'); + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $this->history[0]['request']; + + $this->assertSame('https://authserver.mojang.com/signout', (string)$request->getUri()); + + $this->assertFalse($result); } public function testJoinServer() {