From 0e66eb28590419c54b0faa3c5b8c9755466f857d Mon Sep 17 00:00:00 2001 From: Anatoliy Poloz Date: Sat, 24 Jan 2026 17:24:40 +0300 Subject: [PATCH] feat: update authentication flow and OAuth2 implementation - Refactor AlfaBankConnector to use Saloon's OAuth2 traits - Update authentication methods to use new OAuth2 implementation - Add tests for authorization URL generation, token exchange, and refresh - Replace custom GetTokenRequest with Saloon's built-in OAuth2 requests Co-authored-by: Qwen-Coder --- src/Core/AlfaBankConnector.php | 26 +++-- tests/Feature/Auth/AuthenticationTest.php | 131 +++++++++++++++------- 2 files changed, 106 insertions(+), 51 deletions(-) diff --git a/src/Core/AlfaBankConnector.php b/src/Core/AlfaBankConnector.php index 425c3f5..89ee393 100644 --- a/src/Core/AlfaBankConnector.php +++ b/src/Core/AlfaBankConnector.php @@ -101,24 +101,28 @@ class AlfaBankConnector extends Connector implements HasPagination, OAuth2Api // login() kept for ClientCredentials compatibility (legacy/simple flow) public function login(?string $clientId = null, ?string $clientSecret = null): static { - // Allow overriding config via method arguments - $clientId = $clientId ?? $this->alfaConfig->clientId; - $clientSecret = $clientSecret ?? $this->alfaConfig->clientSecret; + $oauthConfig = $this->oauthConfig(); - if (!$clientId || !$clientSecret) { + // Allow overriding credentials via method arguments + if ($clientId) { + $oauthConfig->setClientId($clientId); + } + if ($clientSecret) { + $oauthConfig->setClientSecret($clientSecret); + } + + if (empty($oauthConfig->getClientId()) || empty($oauthConfig->getClientSecret())) { throw new \RuntimeException("Client ID/Secret not configured."); } - $request = new GetTokenRequest( - clientId: $clientId, - clientSecret: $clientSecret, - grantType: 'client_credentials' - ); + $request = new \Saloon\Http\OAuth2\GetClientCredentialsTokenRequest($oauthConfig); $response = $this->send($request); - $data = $response->dto(); + + // Use the trait's helper to parse the token + $authenticator = $this->createOAuthAuthenticatorFromResponse($response); - $this->authenticate(new \Saloon\Http\Auth\TokenAuthenticator($data->access_token)); + $this->authenticate($authenticator); return $this; } diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php index 12c2abc..475ae83 100644 --- a/tests/Feature/Auth/AuthenticationTest.php +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -2,53 +2,104 @@ use Apoloz\AlfaBank\Core\AlfaBankConnector; use Apoloz\AlfaBank\Core\AlfaConfig; -use Apoloz\AlfaBank\Requests\Auth\GetTokenRequest; use Saloon\Http\Faking\MockResponse; +use Saloon\Http\OAuth2\GetAccessTokenRequest; +use Saloon\Http\OAuth2\GetRefreshTokenRequest; -test('can authenticate and get token', function () { - $mockClient = new \Saloon\Http\Faking\MockClient([ - GetTokenRequest::class => MockResponse::make([ - 'access_token' => 'test-access-token', - 'refresh_token' => 'test-refresh-token', - 'expires_in' => 3600, - 'token_type' => 'Bearer', - ], 200), - ]); - +test('can generate authorization url', function () { $config = new AlfaConfig( - baseUrl: 'https://test.com', - certificatePath: __DIR__ . '/../../test_key.pem', // Use any existing file - privateKeyPath: __DIR__ . '/../../test_key.pem' - ); - $connector = new AlfaBankConnector($config); - $connector->withMockClient($mockClient); - - $request = new GetTokenRequest('client-id', 'client-secret', null, null, 'client_credentials'); - $response = $connector->send($request); - - expect($response->status())->toBe(200) - ->and($response->dto()->access_token)->toBe('test-access-token'); -}); - -test('can login and set token', function () { - $mockClient = new \Saloon\Http\Faking\MockClient([ - GetTokenRequest::class => MockResponse::make([ - 'access_token' => 'test-access-token', - 'refresh_token' => 'test-refresh-token', - 'expires_in' => 3600, - 'token_type' => 'Bearer', - ], 200), - ]); - - $config = new AlfaConfig( - baseUrl: 'https://test.com', + baseUrl: 'https://sandbox.alfabank.ru', certificatePath: __DIR__ . '/../../test_key.pem', - privateKeyPath: __DIR__ . '/../../test_key.pem' + privateKeyPath: __DIR__ . '/../../test_key.pem', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + redirectUri: 'https://callback.url' + ); + $connector = new AlfaBankConnector($config); + + $url = $connector->getAuthorizationUrl(['beneficiaries']); + + expect($url)->toContain('https://id-sandbox.alfabank.ru/oidc/authorize') + ->toContain('client_id=test-client-id') + ->toContain('redirect_uri=' . urlencode('https://callback.url')) + ->toContain('scope=beneficiaries'); +}); + +test('can exchange code for token', function () { + $mockClient = new \Saloon\Http\Faking\MockClient([ + GetAccessTokenRequest::class => MockResponse::make([ + 'access_token' => 'new-access-token', + 'refresh_token' => 'new-refresh-token', + 'expires_in' => 3600, + 'token_type' => 'Bearer', + ], 200), + ]); + + $config = new AlfaConfig( + baseUrl: 'https://sandbox.alfabank.ru', + certificatePath: __DIR__ . '/../../test_key.pem', + privateKeyPath: __DIR__ . '/../../test_key.pem', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + redirectUri: 'https://callback.url' ); $connector = new AlfaBankConnector($config); $connector->withMockClient($mockClient); - $connector->login('client-id', 'client-secret'); + $authenticator = $connector->getAccessToken('auth-code-123'); - expect($connector->getAuthenticator())->not->toBeNull(); + expect($authenticator->getAccessToken())->toBe('new-access-token') + ->and($authenticator->getRefreshToken())->toBe('new-refresh-token'); }); + +test('can refresh token', function () { + $mockClient = new \Saloon\Http\Faking\MockClient([ + GetRefreshTokenRequest::class => MockResponse::make([ + 'access_token' => 'refreshed-access-token', + 'refresh_token' => 'refreshed-refresh-token', + 'expires_in' => 3600, + 'token_type' => 'Bearer', + ], 200), + ]); + + $config = new AlfaConfig( + baseUrl: 'https://sandbox.alfabank.ru', + certificatePath: __DIR__ . '/../../test_key.pem', + privateKeyPath: __DIR__ . '/../../test_key.pem', + clientId: 'test-client-id', + clientSecret: 'test-client-secret' + ); + $connector = new AlfaBankConnector($config); + $connector->withMockClient($mockClient); + + $authenticator = $connector->refreshAccessToken('old-refresh-token'); + + expect($authenticator->getAccessToken())->toBe('refreshed-access-token') + ->and($authenticator->getRefreshToken())->toBe('refreshed-refresh-token'); +}); + +test('can login with client credentials', function () { + $mockClient = new \Saloon\Http\Faking\MockClient([ + \Saloon\Http\OAuth2\GetClientCredentialsTokenRequest::class => MockResponse::make([ + 'access_token' => 'cc-access-token', + 'expires_in' => 3600, + 'token_type' => 'Bearer', + ], 200), + ]); + + $config = new AlfaConfig( + baseUrl: 'https://sandbox.alfabank.ru', + certificatePath: __DIR__ . '/../../test_key.pem', + privateKeyPath: __DIR__ . '/../../test_key.pem', + clientId: 'test-client-id', + clientSecret: 'test-client-secret' + ); + $connector = new AlfaBankConnector($config); + $connector->withMockClient($mockClient); + + $connector->login(); + + expect($connector->getAuthenticator())->not->toBeNull() + ->and($connector->getAuthenticator()->getAccessToken())->toBe('cc-access-token'); +}); +