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 <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-01-24 17:24:40 +03:00
parent bb5f91d399
commit 0e66eb2859
2 changed files with 106 additions and 51 deletions

View File

@@ -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;
}

View File

@@ -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');
});