diff --git a/.docs/README.md b/.docs/README.md
index b5e8d2c..3bbb6ac 100644
--- a/.docs/README.md
+++ b/.docs/README.md
@@ -56,7 +56,7 @@ List of all providers is [here](https://github.com/thephpleague/oauth2-client/bl
This example uses Google as provider with integration through [league/oauth2-google](https://github.com/thephpleague/oauth2-google)
-Install package
+### Install package
```bash
composer require league/oauth2-google
@@ -64,7 +64,7 @@ composer require league/oauth2-google
Get your oauth2 credentials (`clientId` and `clientSecret`) from [Google website](https://developers.google.com/identity/protocols/OpenIDConnect#registeringyourapp)
-Register flow
+### Register flow
```neon
google:
@@ -77,7 +77,9 @@ extensions:
google: Contributte\OAuth2Client\DI\GoogleAuthExtension
```
-Create a control which can handle authentication and authorization
+### A) Create custom control
+
+Create custom control which can handle authentication and authorization.
```php
use Contributte\OAuth2Client\Flow\Google\GoogleAuthCodeFlow;
@@ -158,4 +160,64 @@ Create link to authentication action
Sign in with Google
```
+### B) Use `GenericAuthControl`
+
+Add `GenericAuthControl` control to sign presenter
+
+```php
+use Nette\Application\UI\Presenter;
+use Contributte\OAuth2Client\Flow\Google\GoogleAuthCodeFlow;
+use League\OAuth2\Client\Provider\GoogleUser;
+use League\OAuth2\Client\Token\AccessToken;
+
+class SignPresenter extends Presenter
+{
+
+ public function actionGoogleAuthenticate(): void
+ {
+ $this['googleButton']->authenticate();
+ }
+
+ public function actionGoogleAuthorize(): void
+ {
+ $this['googleButton']->authorize();
+ }
+
+ protected function createComponentGoogleButton(): GoogleButton
+ {
+ $authControl = new GenericAuthControl(
+ $this->googleAuthFlow,
+ $this->presenter->link('//:Sign:googleAuthorize')
+ );
+ $authControl->setTemplate(__DIR__ . "/googleAuthLatte.latte");
+ $authControl->onAuthenticate[] = function(AccessToken $accessToken, GoogleUser $user) {
+ // TODO - try sign in user with it's email ($owner->getEmail())
+ }
+ $authControl->onFail[] = function() {
+ // TODO - Identity provider failure, cannot get information about user
+ }
+ return $authControl;
+ }
+
+}
+```
+
+Create custom template for authentication control.
+
+```latte
+Sign in with Google
+```
+
+Use control in presenter template.
+
+```latte
+{control googleButton}
+```
+
+Or create link to authentication action in presenter template
+
+```latte
+Sign in with Google
+```
+
That's all!
diff --git a/composer.json b/composer.json
index 5e9a62a..7264077 100644
--- a/composer.json
+++ b/composer.json
@@ -22,6 +22,7 @@
},
"require-dev": {
"nette/di": "^3.0.0",
+ "nette/application": "^3.1.0",
"league/oauth2-facebook": "^2.0.5",
"league/oauth2-google": "^3.0.3",
"mockery/mockery": "^1.3.3",
diff --git a/src/UI/Components/GenericAuthControl.latte b/src/UI/Components/GenericAuthControl.latte
new file mode 100644
index 0000000..a78713b
--- /dev/null
+++ b/src/UI/Components/GenericAuthControl.latte
@@ -0,0 +1 @@
+Login
diff --git a/src/UI/Components/GenericAuthControl.php b/src/UI/Components/GenericAuthControl.php
new file mode 100644
index 0000000..00417d1
--- /dev/null
+++ b/src/UI/Components/GenericAuthControl.php
@@ -0,0 +1,92 @@
+ */
+ public $onAuthenticated = [];
+
+ /** @var array */
+ public $onFailed = [];
+
+ public function __construct(AuthCodeFlow $authCodeFlow, ?string $redirectUri = null)
+ {
+ $this->authCodeFlow = $authCodeFlow;
+ $this->redirectUri = $redirectUri;
+ }
+
+ public function setTemplate(string $templatePath): void
+ {
+ $this->templatePath = $templatePath;
+ }
+
+ public function handleAuthenticate(): void
+ {
+ $this->authenticate();
+ }
+
+ public function authenticate(): void
+ {
+ $this->getPresenter()->redirectUrl(
+ $this->authCodeFlow->getAuthorizationUrl(['redirect_uri' => $this->redirectUri])
+ );
+ }
+
+ public function authorize(): ?ResourceOwnerInterface
+ {
+ try {
+ $accessToken = $this->authCodeFlow->getAccessToken($this->getPresenter()->getHttpRequest()->getQuery());
+ if (!$accessToken instanceof AccessToken) {
+ throw new UnexpectedValueException();
+ }
+
+ $user = $this->authCodeFlow->getProvider()->getResourceOwner($accessToken);
+ $this->authenticationSucceed($accessToken, $user);
+ return $user;
+ } catch (IdentityProviderException $e) {
+ $this->authenticationFailed();
+ }
+
+ return null;
+ }
+
+ protected function authenticationFailed(): void
+ {
+ $this->onFailed();
+ }
+
+ protected function authenticationSucceed(AccessToken $accessToken, ResourceOwnerInterface $user): void
+ {
+ $this->onAuthenticated($accessToken, $user);
+ }
+
+ public function render(): void
+ {
+ $template = $this->getTemplate();
+ if (!$template instanceof Template) {
+ throw new UnexpectedValueException();
+ }
+
+ $template->render($this->templatePath ?? __DIR__ . '/GenericAuthControl.latte');
+ }
+
+}
diff --git a/tests/cases/UI/Components/GenericAuthControl.phpt b/tests/cases/UI/Components/GenericAuthControl.phpt
new file mode 100644
index 0000000..98bb5c8
--- /dev/null
+++ b/tests/cases/UI/Components/GenericAuthControl.phpt
@@ -0,0 +1,105 @@
+shouldReceive('getAUthorizationUrl')
+ ->with(['redirect_uri' => 'https://localhost/redirect'])
+ ->andReturn('https://localhost/auth');
+
+ $authControl = new GenericAuthControl($flow, 'https://localhost/redirect');
+ $presenter = new TestPresenter($authControl);
+
+ Assert::exception(function () use ($authControl) {
+ $authControl->authenticate();
+ }, AbortException::class);
+
+ /** @var RedirectResponse $response */
+ $response = $presenter->response;
+ Assert::type(RedirectResponse::class, $response);
+ Assert::equal('https://localhost/auth', $response->getUrl());
+});
+
+Toolkit::test(function (): void {
+ $token = Mockery::mock(AccessToken::class);
+
+ $provider = Mockery::mock(AbstractProvider::class);
+ $provider->shouldReceive('getResourceOwner')
+ ->andReturn(new GenericResourceOwner([], 1));
+
+ $flow = Mockery::mock(AuthCodeFlow::class);
+ $flow->shouldReceive('getAccessToken')
+ ->with(['code' => '123'])
+ ->andReturn($token);
+ $flow->shouldReceive('getProvider')
+ ->andReturn($provider);
+
+ $request = new Request(new UrlScript('https://localhost/redirect?code=123'));
+
+ $events = [];
+
+ $authControl = new GenericAuthControl($flow, 'https://localhost/redirect');
+ $authControl->onAuthenticated[] = function ($accessToken, $user) use (&$events) {
+ $events[] = ['onAuthenticated', $accessToken, $user];
+ };
+ $authControl->onFailed[] = function () use (&$events) {
+ $events[] = ['onFailed'];
+ };
+ new TestPresenter($authControl, $request);
+
+ $user = $authControl->authorize();
+
+ Assert::type(GenericResourceOwner::class, $user);
+
+ Assert::count(1, $events);
+ Assert::equal('onAuthenticated', $events[0][0]);
+ Assert::type(AccessToken::class, $events[0][1]);
+ Assert::type(GenericResourceOwner::class, $events[0][2]);
+});
+
+Toolkit::test(function (): void {
+ $flow = Mockery::mock(AuthCodeFlow::class);
+ $flow->shouldReceive('getAccessToken')
+ ->andThrow(new IdentityProviderException('error', 1, null));
+
+ $request = new Request(new UrlScript('https://localhost/redirect?code=123'));
+
+ $events = [];
+
+ $authControl = new GenericAuthControl($flow, 'https://localhost/redirect');
+ $authControl->onAuthenticated[] = function ($accessToken, $user) use (&$events) {
+ $events[] = ['onAuthenticated', $accessToken, $user];
+ };
+ $authControl->onFailed[] = function () use (&$events) {
+ $events[] = ['onFailed'];
+ };
+ new TestPresenter($authControl, $request);
+
+ $user = $authControl->authorize();
+
+ Assert::null($user);
+
+ Assert::count(1, $events);
+ Assert::equal('onFailed', $events[0][0]);
+});
diff --git a/tests/fixtures/Presenter/TestPresenter.php b/tests/fixtures/Presenter/TestPresenter.php
new file mode 100644
index 0000000..638798d
--- /dev/null
+++ b/tests/fixtures/Presenter/TestPresenter.php
@@ -0,0 +1,52 @@
+injectPrimary(
+ null,
+ null,
+ null,
+ $request ?? new HttpRequest(new UrlScript('http://localhost/page')),
+ $response ?? new HttpResponse()
+ );
+ $this->component = $component;
+ $this->getComponent('subject');
+ }
+
+ protected function createComponentSubject(): Component
+ {
+ return $this->component;
+ }
+
+ public function sendResponse(Response $response): void
+ {
+ $this->response = $response;
+ parent::sendResponse($response);
+ }
+
+}