├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config
└── botauth.php
├── example
└── ExampleProvider.php
└── src
├── AbstractProvider.php
├── BotAuthManager.php
├── BotAuthServiceProvider.php
├── Database
└── migrations
│ └── 2019_06_04_080507_create_bot_auths_table.php
├── Events
└── MessageNewEvent.php
├── Facades
└── BotAuth.php
├── Http
└── Controllers
│ └── BotAuthController.php
├── Models
└── BotAuth.php
├── Providers
├── FacebookProvider.php
├── TelegramProvider.php
└── VkontakteProvider.php
├── Resources
└── views
│ └── botauth.blade.php
├── Routes
└── web.php
├── Traits
├── BotAuthControllerTrait.php
└── BotAuthUserTrait.php
└── Translations
└── en
└── callback.php
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | .DS_STORE
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Timur
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BotAuth - вход и регистрация при помощи ботов
2 |
3 | Пакет позволяет реализовать аутентификацию при помощи ботов в соц. сетях.
4 | Основная задача упростить аутентификацию для пользователей, которые используют мобильные устройства для входа на сайт через соц. сеть.
5 |
6 | Ссылки вида:
7 |
8 | * https://vk.me/...
9 | * https://t.me/...
10 | * https://m.me/...
11 |
12 | откроют мобильное приложение для начала диалога с ботом. Посетителю не придется повторно вводить логин и пароль в браузере.
13 |
14 | Возможно подключить ботов:
15 |
16 | * Вконтакте
17 | * Telegram
18 | * FaceBook
19 | * Ваш собственный провайдер (пример ниже)
20 |
21 | [Demo https://laravel.zetrider.ru/](https://laravel.zetrider.ru/)
22 | ____
23 |
24 | ## Установка:
25 |
26 | 1. composer require zetrider/botauth
27 |
28 | 2. Подключить пакет в config/app.php
29 | * Провайдер
30 | ```ZetRider\BotAuth\BotAuthServiceProvider::class,```
31 | * Фасад (Алиас)
32 |
33 | ```'BotAuth' => ZetRider\BotAuth\Facades\BotAuth::class,```
34 |
35 | 3. Скопировать конфиг. файл
36 |
37 | ``` php artisan vendor:publish --tag=botauth-config ```
38 |
39 | при необходимости
40 |
41 | ``` php artisan vendor:publish --tag=botauth-views ```
42 |
43 | ``` php artisan vendor:publish --tag=botauth-migrations ```
44 |
45 | 4. Указать для нужных соц. сетей ссылку в параметре link.
46 |
47 | * https://vk.me/...
48 | * https://t.me/...
49 | * https://m.me/...
50 |
51 | 5. Заполнить ENV файл ключами ботов
52 | ```BOTAUTH_VKONTAKTE_API_SECRET```
53 |
54 | ```BOTAUTH_VKONTAKTE_API_TOKEN```
55 |
56 | ```BOTAUTH_VKONTAKTE_API_CONFIRM```
57 |
58 | ```BOTAUTH_TELEGRAM_API_TOKEN```
59 |
60 | ```BOTAUTH_TELEGRAM_PROXY```
61 |
62 | ```BOTAUTH_FACEBOOK_API_SECRET```
63 |
64 | ```BOTAUTH_FACEBOOK_API_TOKEN```
65 |
66 | ```BOTAUTH_FACEBOOK_API_CONFIRM```
67 |
68 | 6. Запустить миграции
69 | ``` php artisan migrate ```
70 |
71 | 7. В Middleware VerifyCsrfToken добавить исключение адреса для callback, по умолчанию botauth/callback/*'
72 | ```
73 | protected $except = [
74 | 'botauth/callback/*' // Except callback Csrf middleware
75 | ];
76 | ```
77 |
78 | 8. Для вашей модели User добавьте трейт:
79 | ```use ZetRider\BotAuth\Traits\BotAuthUserTrait;```
80 | который добавит отношение с логинами пользователя из соц. сетей
81 |
82 | ## Подключение ботов:
83 |
84 | ### Вконтакте
85 | 1. Откройте настройки своего сообщества или создайте новое https://vk.com/groups?w=groups_create
86 | 2. В настройках сообщества откройте райздел "Настройки" - "Работа с API"
87 | 3. Создайте ключ доступа, выберите пункт "Разрешить приложению доступ к сообщениям сообщества", запишите ключ, его нужно указать в .env ```BOTAUTH_VKONTAKTE_API_TOKEN```
88 | 4. На той же странице выберите Callback API, выберите "Версия API" 5.95, укажите в поле "Адрес" callback адрес вашего сайта, пример по умолчанию https://DOMAIN/botauth/callback/vkontakte
89 | 5. Ниже укажите строку, которую должен вернуть сервер в .env ```BOTAUTH_VKONTAKTE_API_CONFIRM```
90 | 6. В поле "Секретный ключ" придумайте любой секретный ключ, укажите в .env ```BOTAUTH_VKONTAKTE_API_SECRET```
91 | 7. После заполнения всех ключей в .env нажмите кнопку "Подтверидть"
92 | 8. На этой же странице откройте вкладку "Типы событий", нужно выбрать "Входящие сообщения"
93 | 9. Откройте настройки сообщества, пункт "Сообщения", включите "сообщения сообщества"
94 | 10. Откройте настройки сообщества, пункт "Сообщения" - "Настройки для бота", включите "Возможности ботов"
95 |
96 | Бот готов к работе.
97 |
98 | Пример прямой ссылки на диалог с ботом https://vk.me/zetcode
99 |
100 | ### Telegram
101 | 1. Создайте своего бота через @BotFather
102 | 2. Запомните ключ, укажите в .env ```BOTAUTH_TELEGRAM_API_TOKEN```
103 | 3. Добавьте веб хук через
104 | ```https://api.telegram.org/botYOUR_TOKEN/setWebhook?url=https://DOMAIN/botauth/callback/telegram```
105 | где YOUR_TOKEN ваш токен.
106 | 4. При необходимости укажите прокси в .env
107 | ```BOTAUTH_TELEGRAM_PROXY```, например socks5h://127.0.0.1:1080
108 |
109 | Бот готов к работе.
110 |
111 | Пример прямой ссылки на диалог с ботом https://t.me/BotAuthBot
112 |
113 | ### Facebook
114 |
115 | 1. У вас должна быть создана страница, если ее нет, добавьте https://www.facebook.com/pages/creation/?ref_type=universal_creation_hub
116 | 2. Добвьте новое приложение https://developers.facebook.com/apps/
117 | 3. В настройках приложение выберите "Основное", скопируйте "Секрет приложения" в .env ```BOTAUTH_FACEBOOK_API_SECRET```
118 | 4. В настройках приложение нужно добавить продукт "Messenger"
119 | 5. В настройках продукта "Messenger" создайте токен доступа, укажите его в .env BOTAUTH_FACEBOOK_API_TOKEN
120 | 6. В настройках продукта "Messenger" создайте веб хук, в URL обратного вызова укажите https://DOMAIN/botauth/callback/facebook
121 | в поле "Подтвердите маркер" укажите произвольный текст, сохраните в .env BOTAUTH_FACEBOOK_API_CONFIRM
122 | в опциях "Поля подписки" выберите "messages"
123 | нажмите "Подтвердить"
124 | 6. После подтверждения сервера в настройках веб хуков выберите страницу, нажмите "Подписаться" выбран нужную страницу
125 | 7. В окне "Проверка приложения Messenger" рядом с пунктом "pages_messaging" нажмите "Добавить к заявке"
126 | 8. Бот уже готов к работе, но доступен только для администраторов. После подтверждения приложения, он станет доступен для всех посетителей. Отправьте приложение на модерацию.
127 |
128 | Пример прямой ссылки на диалог с ботом https://m.me/zetridercode
129 |
130 | ___
131 |
132 | ## Важно:
133 | 1. Сайт должен работать по https
134 | 2. Facebook бот возвращает PSID, который не соответствует публичному ID пользователя.
135 | 3. По умолчанию контроллер бота работает с моделью \App\User. Если у вас другой случай, просто создайте свой контроллер и модель на основе примеров из репозитория.
136 |
137 | ## Как добавить свой провайдер:
138 | Создайте свой класс, который наследует абстрактный класс ```ZetRider\BotAuth\AbstractProvider```
139 |
140 | Пример example/ExampleProvider.php
141 |
142 | Добавьте в сервис провайдер, например AppServiceProvider в методе boot
143 |
144 | ```php
145 | // Register example proider
146 | BotAuth::extend('example', function() {
147 | return new \Path\To\Your\Class\ExampleProvider();
148 | });
149 | ```
150 | Провайдер будет обрабатывать запросы в callback по адресу /botauth/callback/example
151 |
152 | ## События
153 | Событие при успешной обработке нового сообщения от бота
154 |
155 | ```php
156 | // Catch bot callback
157 | \Event::listen(\ZetRider\BotAuth\Events\MessageNewEvent::class, function($event)
158 | {
159 | $provider = $event->provider; // ZetRider\BotAuth\AbstractProvider
160 |
161 | $slug = $provider->getProviderSlug();
162 | $data = $provider->getCallbackResponse();
163 | $user = $provider->getUser();
164 | $text = $provider->getText();
165 |
166 | // You can send a message
167 | // $provider->sendMessage(__('Back to web site'));
168 | });
169 | ```
170 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zetrider/botauth",
3 | "description": "authentication by bot",
4 | "keywords": ["auth", "laravel", "bot", "facebook", "telegram", "vk", "vkontakte"],
5 | "license": "MIT",
6 | "homepage": "https://github.com/zetrider/BotAuth",
7 | "authors": [
8 | {
9 | "name": "ZetRider",
10 | "email": "zetrider@bk.ru"
11 | }
12 | ],
13 | "require": {
14 | "php": "^7.0",
15 | "guzzlehttp/guzzle": "~6.0"
16 | },
17 | "autoload": {
18 | "psr-4": {
19 | "ZetRider\\BotAuth\\": "src/"
20 | }
21 | },
22 | "extra": {
23 | "laravel": {
24 | "providers": [
25 | "ZetRider\\BotAuth\\BotAuthServiceProvider"
26 | ],
27 | "aliases": {
28 | "BotAuth": "ZetRider\\BotAuth\\Facades\\BotAuth"
29 | }
30 | }
31 | },
32 | "minimum-stability": "dev",
33 | "prefer-stable": true
34 | }
--------------------------------------------------------------------------------
/config/botauth.php:
--------------------------------------------------------------------------------
1 | [
6 | 'link' => 'https://vk.me/...',
7 | 'api_secret' => env('BOTAUTH_VKONTAKTE_API_SECRET', ''),
8 | 'api_token' => env('BOTAUTH_VKONTAKTE_API_TOKEN', ''),
9 | 'api_confirm' => env('BOTAUTH_VKONTAKTE_API_CONFIRM', ''),
10 | 'api_user_fields' => [
11 | 'id',
12 | 'first_name',
13 | 'last_name',
14 | 'nickname',
15 | 'screen_name',
16 | 'photo_max_orig',
17 | 'city',
18 | 'country',
19 | 'counters',
20 | ],
21 | ],
22 | 'telegram' => [
23 | 'link' => 'https://t.me/...',
24 | 'api_token' => env('BOTAUTH_TELEGRAM_API_TOKEN', ''),
25 | 'proxy' => env('BOTAUTH_TELEGRAM_PROXY', ''),
26 | ],
27 | 'facebook' => [
28 | 'link' => 'https://m.me/...',
29 | 'api_secret' => env('BOTAUTH_FACEBOOK_API_SECRET', ''),
30 | 'api_token' => env('BOTAUTH_FACEBOOK_API_TOKEN', ''),
31 | 'api_confirm' => env('BOTAUTH_FACEBOOK_API_CONFIRM', ''),
32 | 'api_user_fields' => [
33 | 'id',
34 | 'name',
35 | 'first_name',
36 | 'last_name',
37 | 'profile_pic',
38 | 'locale',
39 | 'timezone',
40 | 'gender',
41 | ],
42 | ],
43 |
44 | ];
45 |
--------------------------------------------------------------------------------
/example/ExampleProvider.php:
--------------------------------------------------------------------------------
1 | provider_slug;
24 | }
25 |
26 | /**
27 | * Get Callback Response From Bot
28 | *
29 | * @return array
30 | */
31 | public function getCallbackResponse()
32 | {
33 | return [];
34 | }
35 |
36 | /**
37 | * Get the user
38 | *
39 | * @param string $token
40 | * @return array
41 | * int array[id]
42 | * string array[login]
43 | * string array[first_name]
44 | * string array[last_name]
45 | * string array[photo]
46 | * array array[raw]
47 | */
48 | public function getUser()
49 | {
50 | return [];
51 | }
52 |
53 | /**
54 | * Get text
55 | *
56 | * @return string
57 | */
58 | public function getText()
59 | {
60 | return '';
61 | }
62 |
63 | /**
64 | * Send answer
65 | *
66 | * @param string $message
67 | * @return array
68 | */
69 | public function sendMessage($message)
70 | {
71 | return null;
72 | }
73 |
74 | /**
75 | * Callback
76 | *
77 | * @return exception|self
78 | */
79 | public function callback()
80 | {
81 | dd('Hi');
82 | return $this;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/AbstractProvider.php:
--------------------------------------------------------------------------------
1 | http_client)
85 | {
86 | $params = [
87 | 'http_errors' => false,
88 | ];
89 | if (method_exists($this, 'getProxy') and !empty($this->getProxy()))
90 | {
91 | $params['proxy'] = $this->getProxy();
92 | }
93 | $this->http_client = new Client($params);
94 | }
95 |
96 | return $this->http_client;
97 | }
98 |
99 | /**
100 | * Get Gizzle Post Key
101 | *
102 | * @return string
103 | */
104 | protected function getGuzzlePostKey()
105 | {
106 | return (version_compare(ClientInterface::VERSION, '6') === 1) ? 'form_params' : 'body';
107 | }
108 |
109 | /**
110 | * Get Gizzle Post Key
111 | *
112 | * @param Guzzle response $response
113 | * @return array
114 | */
115 | protected function getGuzzleResponseArray($response)
116 | {
117 | $body = (String) $response->getBody();
118 | return json_decode($body, true) ?? [];
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/BotAuthManager.php:
--------------------------------------------------------------------------------
1 | app->singleton('BotAuth', function ($app) {
19 | return new BotAuthManager($app);
20 | });
21 | }
22 |
23 | /**
24 | * Bootstrap services.
25 | *
26 | * @return void
27 | */
28 | public function boot()
29 | {
30 | // php artisan vendor:publish --tag=botauth-config
31 | $this->publishes([
32 | __DIR__.'/../config/botauth.php' => config_path('botauth.php'),
33 | ], 'botauth-config');
34 |
35 | // php artisan vendor:publish --tag=botauth-migrations
36 | $this->publishes([
37 | __DIR__.'/Database/migrations/' => database_path('migrations')
38 | ], 'botauth-migrations');
39 |
40 | // php artisan vendor:publish --tag=botauth-views
41 | $this->publishes([
42 | __DIR__.'/Resources/views' => resource_path('views/vendor/botauth'),
43 | ], 'botauth-views');
44 |
45 | // php artisan vendor:publish --tag=botauth-translations
46 | $this->publishes([
47 | __DIR__.'/Translations' => resource_path('lang/vendor/botauth'),
48 | ], 'botauth-translations');
49 |
50 | $this->loadTranslationsFrom(__DIR__.'/Translations', 'botauth');
51 |
52 | $this->loadMigrationsFrom(__DIR__.'/Database/migrations');
53 |
54 | $this->loadViewsFrom(__DIR__.'/Resources/views', 'botauth');
55 |
56 | $this->loadRoutesFrom(__DIR__.'/Routes/web.php');
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/Database/migrations/2019_06_04_080507_create_bot_auths_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('user_id')->nullable();
19 | $table->string('provider');
20 | $table->string('external_id');
21 | $table->string('secret')->nullable();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('bot_auths');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Events/MessageNewEvent.php:
--------------------------------------------------------------------------------
1 | provider = $provider;
29 | }
30 |
31 | /**
32 | * Get the channels the event should broadcast on.
33 | *
34 | * @return \Illuminate\Broadcasting\Channel|array
35 | */
36 | public function broadcastOn()
37 | {
38 | return new PrivateChannel('botauth');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Facades/BotAuth.php:
--------------------------------------------------------------------------------
1 | getSecretPrefix() . uniqid();
24 | $logins = null;
25 | if(Auth::check())
26 | {
27 | $logins = Auth::User()->botAuth()->get();
28 | }
29 | return view('botauth::botauth', [
30 | 'secret' => $secret,
31 | 'logins' => $logins,
32 | ]);
33 | }
34 |
35 | /**
36 | * Confirm code
37 | *
38 | * @param Illuminate\Http\Request $request
39 | * @return \Illuminate\Http\Response
40 | */
41 | public function check(Request $request)
42 | {
43 | $success = false;
44 | $message = __('User not found');
45 |
46 | if($auth = $this->secretIsset($request->secret))
47 | {
48 | // Login user
49 | Auth::login($auth->user, true);
50 |
51 | $success = true;
52 | $message = __('Success');
53 | }
54 |
55 | if($request->ajax())
56 | {
57 | return response()->json([
58 | 'success' => $success
59 | ]);
60 | }
61 |
62 | return redirect()->back()->with('message', $message);
63 | }
64 |
65 | /**
66 | * Catch callback
67 | *
68 | * @param string $provider driver slug
69 | * @return mixed
70 | */
71 | public function callback($provider)
72 | {
73 | $this->callbackHandler($provider);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Models/BotAuth.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Providers/FacebookProvider.php:
--------------------------------------------------------------------------------
1 | api_version = 'v3.3';
80 | $this->api_endpoint = 'https://graph.facebook.com';
81 | $this->api_secret = $api_secret;
82 | $this->api_token = $api_token;
83 | $this->api_confirm = $api_confirm;
84 | $this->api_user_fields = $api_user_fields;
85 | $this->callback_response = [];
86 | $this->user_data = [];
87 | }
88 |
89 | /**
90 | * Get Provider Slug
91 | *
92 | * @return string
93 | */
94 | public function getProviderSlug()
95 | {
96 | return $this->provider_slug;
97 | }
98 |
99 | /**
100 | * Get Api Version
101 | *
102 | * @return string
103 | */
104 | protected function getApiVersion()
105 | {
106 | return $this->api_version;
107 | }
108 |
109 | /**
110 | * Get Api Secret
111 | *
112 | * @return string
113 | */
114 | protected function getApiSecret()
115 | {
116 | return $this->api_secret;
117 | }
118 |
119 | /**
120 | * Get Api Token
121 | *
122 | * @return string
123 | */
124 | protected function getApiToken()
125 | {
126 | return $this->api_token;
127 | }
128 |
129 | /**
130 | * Get Api Confirm
131 | *
132 | * @return string
133 | */
134 | protected function getApiConfirm()
135 | {
136 | return $this->api_confirm;
137 | }
138 |
139 | /**
140 | * Get Api User Fields
141 | *
142 | * @return string
143 | */
144 | protected function getApiUserFields()
145 | {
146 | $fields = $this->api_user_fields ?? [];
147 | $important = [
148 | 'id',
149 | 'name',
150 | 'first_name',
151 | 'last_name',
152 | 'profile_pic',
153 | ];
154 |
155 | return array_unique(array_merge($fields, $important));
156 | }
157 |
158 | /**
159 | * Get Api Endpoint
160 | *
161 | * @return string
162 | */
163 | protected function getApiEndpoint()
164 | {
165 | return $this->api_endpoint . '/' . $this->getApiVersion();
166 | }
167 |
168 | /**
169 | * Get Callback Response From Bot
170 | *
171 | * @return array
172 | */
173 | public function getCallbackResponse()
174 | {
175 | if (is_array($this->callback_response) and !empty($callback_response))
176 | {
177 | return $callback_response;
178 | }
179 |
180 | $data = [];
181 | $input = file_get_contents('php://input');
182 |
183 | if ($input and $json = json_decode($input, true))
184 | {
185 | $data = $json;
186 | }
187 |
188 | $this->callback_response = $data;
189 |
190 | return $data;
191 | }
192 |
193 | /**
194 | * Get User Data
195 | *
196 | * @return array
197 | */
198 | protected function getUserData()
199 | {
200 | return $this->user_data;
201 | }
202 |
203 | /**
204 | * Check secret key
205 | *
206 | * @param array $data from callback
207 | * @return bool
208 | */
209 | protected function isAllowedRequest($data)
210 | {
211 | $headers = getallheaders();
212 | $header_signature = $headers['X-Hub-Signature'] ?? '';
213 | $raw_post_data = file_get_contents('php://input');
214 | $expected_signature = hash_hmac('sha1', $raw_post_data, $this->getApiSecret());
215 | $signature = '';
216 |
217 | if (strlen($header_signature) == 45 AND substr($header_signature, 0, 5) == 'sha1=')
218 | {
219 | $signature = substr($header_signature, 5);
220 | }
221 |
222 | return hash_equals($signature, $expected_signature);
223 | }
224 |
225 | /**
226 | * Send request to api
227 | *
228 | * @param string $method https://vk.com/dev/methods
229 | * @param array $params of method
230 | * @return array
231 | */
232 | protected function apiRequest($method, $params = [])
233 | {
234 | $params['access_token'] = $this->getApiToken();
235 |
236 | $endpoint = $this->getApiEndpoint() . '/' . $method;
237 | $response = $this->getHttpClient()->request('POST', $endpoint, [
238 | $this->getGuzzlePostKey() => $params
239 | ]);
240 |
241 | return $this->getGuzzleResponseArray($response);
242 | }
243 |
244 | /**
245 | * Send answer
246 | *
247 | * @param string $message
248 | * @return array
249 | */
250 | public function sendMessage($message)
251 | {
252 | $data = $this->getCallbackResponse();
253 |
254 | $messaging = $data['entry'][0]['messaging'][0];
255 | $sender_psid = $messaging['sender']['id'];
256 |
257 | return $this->apiRequest('me/messages', [
258 | 'recipient' => [
259 | 'id' => $sender_psid
260 | ],
261 | 'message' => [
262 | 'text' => $message,
263 | ],
264 | ]);
265 | }
266 |
267 | /**
268 | * Get text
269 | *
270 | * @return string
271 | */
272 | public function getText()
273 | {
274 | $data = $this->getCallbackResponse();
275 | $messaging = $data['entry'][0]['messaging'][0];
276 |
277 | if (isset($messaging['message']['text']))
278 | {
279 | return $messaging['message']['text'];
280 | }
281 |
282 | return '';
283 | }
284 |
285 | /**
286 | * Set User Data
287 | *
288 | * @return array
289 | */
290 | protected function setUserData()
291 | {
292 | $data = $this->getCallbackResponse();
293 | $messaging = $data['entry'][0]['messaging'][0];
294 | $sender_psid = $messaging['sender']['id'];
295 |
296 | $params = [
297 | 'fields' => implode(',', $this->getApiUserFields()),
298 | 'access_token' => $this->getApiToken(),
299 | ];
300 | $endpoint = $this->getApiEndpoint() . '/' . $sender_psid . '?' . http_build_query($params);
301 | $response = $this->getHttpClient()->request('GET', $endpoint);
302 | $user = $this->getGuzzleResponseArray($response);
303 |
304 | if (count($user))
305 | {
306 | $this->user_data = $user;
307 | }
308 |
309 | return $this->user_data;
310 | }
311 |
312 | /**
313 | * Get User Data
314 | *
315 | * @return array
316 | */
317 | public function getUser()
318 | {
319 | $user = [];
320 | $data = $this->getUserData();
321 |
322 | if (count($data))
323 | {
324 | $user['id'] = $data['id'] ?? 0; // it's PSID! not public id. TODO
325 | $user['login'] = '';
326 | $user['first_name'] = $data['first_name'] ?? '';
327 | $user['last_name'] = $data['last_name'] ?? '';
328 | $user['photo'] = $data['profile_pic'] ?? '';
329 | $user['raw'] = $data;
330 | }
331 |
332 | return $user;
333 | }
334 |
335 | /**
336 | * Callback event confirmation
337 | * @return void
338 | */
339 | protected function callbackEventConfirmation()
340 | {
341 | if ($_REQUEST['hub_verify_token'] == $this->getApiConfirm())
342 | {
343 | echo $_REQUEST['hub_challenge'];
344 | }
345 | }
346 |
347 | /**
348 | * Callback event message_new
349 | *
350 | * @return void
351 | */
352 | protected function callbackEventMessageNew()
353 | {
354 | $this->setUserData();
355 | $this->eventUserFound();
356 | }
357 |
358 | /**
359 | * Callback
360 | *
361 | * @return exception|self
362 | */
363 | public function callback()
364 | {
365 | $data = $this->getCallbackResponse();
366 |
367 | $hub_mode = $_REQUEST['hub_mode'] ?? '';
368 | $object = $data['object'] ?? '';
369 |
370 | // Need get method
371 | if ($hub_mode == 'subscribe')
372 | {
373 | $this->callbackEventConfirmation();
374 | }
375 | elseif (!$this->isAllowedRequest($data))
376 | {
377 | Abort(401);
378 | }
379 | elseif ($object == 'page')
380 | {
381 | $this->callbackEventMessageNew();
382 | }
383 |
384 | return $this;
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/src/Providers/TelegramProvider.php:
--------------------------------------------------------------------------------
1 | api_endpoint = 'https://api.telegram.org/bot';
59 | $this->api_endpoint_file = 'https://api.telegram.org/file/bot';
60 | $this->api_token = $api_token;
61 | $this->proxy = $proxy;
62 | $this->callback_response = [];
63 | $this->user_data = [];
64 | }
65 |
66 | /**
67 | * Get Provider Slug
68 | *
69 | * @return string
70 | */
71 | public function getProviderSlug()
72 | {
73 | return $this->provider_slug;
74 | }
75 |
76 | /**
77 | * Get Api Token
78 | *
79 | * @return string
80 | */
81 | protected function getApiToken()
82 | {
83 | return $this->api_token;
84 | }
85 | /**
86 | * Get Proxy
87 | *
88 | * @return string|array
89 | */
90 | protected function getProxy()
91 | {
92 | return $this->proxy;
93 | }
94 |
95 | /**
96 | * Get Api Endpoint
97 | *
98 | * @return string
99 | */
100 | protected function getApiEndpoint()
101 | {
102 | return $this->api_endpoint.$this->getApiToken();
103 | }
104 |
105 | /**
106 | * Get Api Endpoint File
107 | *
108 | * @return string
109 | */
110 | protected function getApiEndpointFile()
111 | {
112 | return $this->api_endpoint_file.$this->getApiToken();
113 | }
114 |
115 | /**
116 | * Get User Data
117 | *
118 | * @return array
119 | */
120 | protected function getUserData()
121 | {
122 | return $this->user_data;
123 | }
124 |
125 | /**
126 | * Get Callback Response From Bot
127 | *
128 | * @return array
129 | */
130 | public function getCallbackResponse()
131 | {
132 | if (is_array($this->callback_response) and !empty($callback_response))
133 | {
134 | return $callback_response;
135 | }
136 |
137 | $data = [];
138 | $input = file_get_contents('php://input');
139 |
140 | if ($input and $json = json_decode($input, true))
141 | {
142 | $data = $json;
143 | }
144 |
145 | $this->callback_response = $data;
146 |
147 | return $data;
148 | }
149 |
150 | /**
151 | * Send request to api
152 | *
153 | * @param string $method https://vk.com/dev/methods
154 | * @param array $params of method
155 | * @return array
156 | */
157 | protected function apiRequest($method, $params = [])
158 | {
159 | $endpoint = $this->getApiEndpoint() . '/' . $method;
160 | $response = $this->getHttpClient()->request('POST', $endpoint, [
161 | $this->getGuzzlePostKey() => $params
162 | ]);
163 |
164 | return $this->getGuzzleResponseArray($response);
165 | }
166 |
167 | /**
168 | * Send answer
169 | *
170 | * @param string $message
171 | * @return array
172 | */
173 | public function sendMessage($message)
174 | {
175 | $data = $this->getCallbackResponse();
176 | $chat_id = $data['message']['chat']['id'] ?? 0;
177 |
178 | return $this->apiRequest('sendMessage', [
179 | 'chat_id' => $chat_id,
180 | 'text' => $message,
181 | ]);
182 | }
183 |
184 | /**
185 | * Get text
186 | *
187 | * @return string
188 | */
189 | public function getText()
190 | {
191 | $data = $this->getCallbackResponse();
192 |
193 | if (isset($data['message']['text']))
194 | {
195 | return $data['message']['text'];
196 | }
197 |
198 | return '';
199 | }
200 |
201 | /**
202 | * Get user photo
203 | *
204 | * @return string
205 | */
206 | protected function apiGetUserPhoto($user_id)
207 | {
208 | $url = '';
209 | $get = $this->apiRequest('getUserProfilePhotos', ['user_id' => $user_id]);
210 |
211 | $file_id = $get['result']['photos']['0']['0']['file_id'] ?? null;
212 |
213 | if ($file_id)
214 | {
215 | $get = $this->apiRequest('getFile', ['file_id' => $file_id]);
216 | $path = $get['result']['file_path'] ?? '';
217 |
218 | if (!empty($path))
219 | {
220 | $url = $this->getApiEndpointFile() . '/' . $path;
221 | }
222 | }
223 |
224 | return $url;
225 | }
226 |
227 | /**
228 | * Set User Data
229 | *
230 | * @return array
231 | */
232 | protected function setUserData()
233 | {
234 | $data = $this->getCallbackResponse();
235 |
236 | if (count($data))
237 | {
238 | $this->user_data = $data['message']['from'] ?? [];
239 | }
240 |
241 | return $this->user_data;
242 | }
243 |
244 | /**
245 | * Get User Data
246 | *
247 | * @return array
248 | */
249 | public function getUser()
250 | {
251 | $user = [];
252 | $data = $this->getUserData();
253 |
254 | if (count($data))
255 | {
256 | $user['id'] = $data['id'] ?? 0;
257 | $user['login'] = $data['username'] ?? '';
258 | $user['first_name'] = $data['first_name'] ?? '';
259 | $user['last_name'] = $data['last_name'] ?? '';
260 | $user['photo'] = $this->apiGetUserPhoto($data['id']);
261 | $user['raw'] = $data;
262 | }
263 |
264 | return $user;
265 | }
266 |
267 | /**
268 | * Callback event message_new
269 | *
270 | * @return void
271 | */
272 | protected function callbackEventMessageNew()
273 | {
274 | $this->setUserData();
275 | $this->eventUserFound();
276 | }
277 |
278 | /**
279 | * Callback
280 | *
281 | * @return exception|self
282 | */
283 | public function callback()
284 | {
285 | $data = $this->getCallbackResponse();
286 |
287 | $this->callbackEventMessageNew();
288 |
289 | return $this;
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/src/Providers/VkontakteProvider.php:
--------------------------------------------------------------------------------
1 | api_version = '5.95';
80 | $this->api_endpoint = 'https://api.vk.com/method';
81 | $this->api_secret = $api_secret;
82 | $this->api_token = $api_token;
83 | $this->api_confirm = $api_confirm;
84 | $this->api_user_fields = $api_user_fields;
85 | $this->callback_response = [];
86 | $this->user_data = [];
87 | }
88 |
89 | /**
90 | * Get Provider Slug
91 | *
92 | * @return string
93 | */
94 | public function getProviderSlug()
95 | {
96 | return $this->provider_slug;
97 | }
98 |
99 | /**
100 | * Get Api Verson
101 | *
102 | * @return string
103 | */
104 | protected function getApiVersion()
105 | {
106 | return $this->api_version;
107 | }
108 |
109 | /**
110 | * Get Api Secret
111 | *
112 | * @return string
113 | */
114 | protected function getApiSecret()
115 | {
116 | return $this->api_secret;
117 | }
118 |
119 | /**
120 | * Get Api Token
121 | *
122 | * @return string
123 | */
124 | protected function getApiToken()
125 | {
126 | return $this->api_token;
127 | }
128 |
129 | /**
130 | * Get Api Confirm Code
131 | *
132 | * @return string
133 | */
134 | protected function getApiConfirm()
135 | {
136 | return $this->api_confirm;
137 | }
138 |
139 | /**
140 | * Get Api User Fields
141 | *
142 | * @return string
143 | */
144 | protected function getApiUserFields()
145 | {
146 | $fields = $this->api_user_fields ?? [];
147 | $important = [
148 | 'id',
149 | 'screen_name',
150 | 'first_name',
151 | 'last_name',
152 | 'photo_max_orig',
153 | ];
154 |
155 | return array_unique(array_merge($fields, $important));
156 | }
157 |
158 | /**
159 | * Get Api Endpoint
160 | *
161 | * @return string
162 | */
163 | protected function getApiEndpoint()
164 | {
165 | return $this->api_endpoint;
166 | }
167 |
168 | /**
169 | * Get User Data
170 | *
171 | * @return array
172 | */
173 | protected function getUserData()
174 | {
175 | return $this->user_data;
176 | }
177 |
178 | /**
179 | * Get Callback Response From Bot
180 | *
181 | * @return array
182 | */
183 | public function getCallbackResponse()
184 | {
185 | if (is_array($this->callback_response) and !empty($callback_response))
186 | {
187 | return $callback_response;
188 | }
189 |
190 | $data = [];
191 | $input = file_get_contents('php://input');
192 |
193 | if ($input and $json = json_decode($input, true))
194 | {
195 | $data = $json;
196 | }
197 |
198 | $this->callback_response = $data;
199 |
200 | return $data;
201 | }
202 |
203 | /**
204 | * Check secret key
205 | *
206 | * @param array $data from callback
207 | * @return bool
208 | */
209 | protected function isAllowedRequest($data)
210 | {
211 | return (!empty($data) and $data['secret'] == $this->getApiSecret());
212 | }
213 |
214 | /**
215 | * Send request to api
216 | *
217 | * @param string $method https://vk.com/dev/methods
218 | * @param array $params of method
219 | * @return array
220 | */
221 | protected function apiRequest($method, $params = [])
222 | {
223 | $params['v'] = $this->getApiVersion();
224 | $params['access_token'] = $this->getApiToken();
225 |
226 | $endpoint = $this->getApiEndpoint() . '/' . $method;
227 | $response = $this->getHttpClient()->request('POST', $endpoint, [
228 | $this->getGuzzlePostKey() => $params
229 | ]);
230 |
231 | return $this->getGuzzleResponseArray($response);
232 | }
233 |
234 | /**
235 | * Send message
236 | *
237 | * @param string $message
238 | * @return array
239 | */
240 | public function sendMessage($message)
241 | {
242 | $data = $this->getCallbackResponse();
243 | $peer_id = $data['object']['peer_id'];
244 |
245 | return $this->apiRequest('messages.send', [
246 | 'peer_id' => $peer_id,
247 | 'message' => $message,
248 | 'random_id' => str_replace('.', '', microtime(true)),
249 | ]);
250 | }
251 |
252 | /**
253 | * Get text
254 | *
255 | * @return string
256 | */
257 | public function getText()
258 | {
259 | $data = $this->getCallbackResponse();
260 |
261 | if (isset($data['object']['text']))
262 | {
263 | return $data['object']['text'];
264 | }
265 |
266 | return '';
267 | }
268 |
269 | /**
270 | * Get user
271 | *
272 | * @return array
273 | */
274 | protected function apiGetUser($user_id)
275 | {
276 | $get = $this->apiRequest('users.get', [
277 | 'user_ids' => $user_id,
278 | 'fields' => implode(',', $this->getApiUserFields()),
279 | 'language' => config('app.locale', 'en'),
280 | ]);
281 | $response = $get['response'] ?? [];
282 |
283 | if (!empty($response) and is_array($response[0]))
284 | {
285 | return $response[0];
286 | }
287 |
288 | return [];
289 | }
290 |
291 | /**
292 | * Set User Data
293 | *
294 | * @param int $user_id
295 | * @return array
296 | */
297 | protected function setUserData($user_id)
298 | {
299 | $data = $this->apiGetUser($user_id);
300 |
301 | if (count($data))
302 | {
303 | $this->user_data = $data;
304 | }
305 |
306 | return $this->user_data;
307 | }
308 |
309 | /**
310 | * Get User Data
311 | *
312 | * @return array
313 | */
314 | public function getUser()
315 | {
316 | $user = [];
317 | $data = $this->getUserData();
318 |
319 | if (count($data))
320 | {
321 | $user['id'] = $data['id'] ?? 0;
322 | $user['login'] = $data['screen_name'] ?? '';
323 | $user['first_name'] = $data['first_name'] ?? '';
324 | $user['last_name'] = $data['last_name'] ?? '';
325 | $user['photo'] = $data['photo_max_orig'] ?? '';
326 | $user['raw'] = $data;
327 | }
328 |
329 | return $user;
330 | }
331 |
332 | /**
333 | * Callback event confirmation
334 | * @return void
335 | */
336 | protected function callbackEventConfirmation()
337 | {
338 | echo $this->getApiConfirm();
339 | }
340 |
341 | /**
342 | * Callback event message_new
343 | *
344 | * @return void
345 | */
346 | protected function callbackEventMessageNew()
347 | {
348 | $data = $this->getCallbackResponse();
349 | $from_id = $data['object']['from_id'];
350 |
351 | $this->setUserData($from_id);
352 | $this->eventUserFound();
353 |
354 | echo 'ok';
355 | }
356 |
357 | /**
358 | * Callback
359 | *
360 | * @return exception|self
361 | */
362 | public function callback()
363 | {
364 | $data = $this->getCallbackResponse();
365 |
366 | if (!$this->isAllowedRequest($data))
367 | {
368 | Abort(401);
369 | }
370 |
371 | $event = $data['type'];
372 |
373 | switch ($event)
374 | {
375 | case 'confirmation':
376 | $this->callbackEventConfirmation();
377 | break;
378 | case 'message_new':
379 | $this->callbackEventMessageNew();
380 | break;
381 | default:
382 | Abort(406, 'Unsupported event');
383 | break;
384 | }
385 |
386 | return $this;
387 | }
388 | }
389 |
--------------------------------------------------------------------------------
/src/Resources/views/botauth.blade.php:
--------------------------------------------------------------------------------
1 | {{-- this is an example template, do not use it --}}
2 |
3 |
4 |
5 |
6 |
7 |
8 | BotAuth
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
{{ __('Auth by bot example') }}
20 |
21 | @if (session('message'))
22 |
23 | {{ session('message') }}
24 |
25 | @endif
26 |
27 | @auth
28 |
{{ __('Hi') }} {{ Auth::User()->name }}
29 |
30 | @if($logins)
31 | {{ __('Auth by:') }}
32 | @foreach($logins as $login)
33 |
{{ $login->provider }}, ID: {{ $login->external_id }}
34 | @endforeach
35 | @endif
36 |
37 |
41 | @else
42 |
43 | {{ __('Write this message to the bot:') }} {{ $secret }}
44 |
45 |
46 |
59 |
60 |
{{ __('Select bot') }}
61 |
62 |
VK
63 |
Telegram
64 |
Facebook
65 |
66 |
67 |
68 |
73 |
74 |
94 | @endauth
95 |
96 | https://github.com/zetrider/BotAuth
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/Routes/web.php:
--------------------------------------------------------------------------------
1 | ['web']], function () {
3 | Route::get('/botauth', 'ZetRider\BotAuth\Http\Controllers\BotAuthController@index')->name('botauth.index');
4 | Route::post('/botauth', 'ZetRider\BotAuth\Http\Controllers\BotAuthController@check')->name('botauth.check');
5 | Route::post('/botauth/callback/{provider}', 'ZetRider\BotAuth\Http\Controllers\BotAuthController@callback');
6 | // For facebook confirm server...
7 | Route::get('/botauth/callback/{provider}', 'ZetRider\BotAuth\Http\Controllers\BotAuthController@callback');
8 | });
--------------------------------------------------------------------------------
/src/Traits/BotAuthControllerTrait.php:
--------------------------------------------------------------------------------
1 | secret_prefix;
17 | }
18 |
19 | /**
20 | * Check secret
21 | * @param string $secret
22 | * @return mixed bool|ZetRider\BotAuth\Models\BotAuth
23 | */
24 | protected function secretIsset($secret)
25 | {
26 | if(!empty($secret))
27 | {
28 | // Find network login by secret code
29 | $auth = BotAuthModel::where('secret', $secret)->whereHas('user')->first();
30 | if($auth and $auth->user)
31 | {
32 | return $auth;
33 | }
34 | }
35 |
36 | return false;
37 | }
38 |
39 | /**
40 | * Callback hanlder
41 | * @param string $provider key
42 | * @return void|exception
43 | */
44 | protected function callbackHandler($provider)
45 | {
46 | // Call driver
47 | $provider = BotAuth::driver($provider)->callback();
48 |
49 | // Get User data
50 | $userData = $provider->getUser();
51 |
52 | if(is_array($userData) and !empty($userData))
53 | {
54 | // Check auth code
55 | $secret = trim($provider->getText());
56 | if(!Str::startsWith($secret, $this->getSecretPrefix()))
57 | {
58 | // To send a message back
59 | $provider->sendMessage(__('botauth::callback.code_isnot_correct'));
60 | return;
61 | }
62 |
63 | // To send a message back
64 | $provider->sendMessage(__('botauth::callback.return_back'));
65 |
66 | // Save auth code
67 | $botAuth = BotAuthModel::updateOrCreate(
68 | [
69 | 'provider' => $provider->getProviderSlug(),
70 | 'external_id' => $userData['id'],
71 | ],
72 | [
73 | 'secret' => $secret,
74 | ]
75 | );
76 |
77 | // Find user
78 | $user = User::whereHas('botAuth', function($query) use ($botAuth) {
79 | $query->where('id', $botAuth->id);
80 | })->first();
81 |
82 | // Create user
83 | if(!$user)
84 | {
85 | // Bot doesn't know email
86 | $email = $userData['email'] ?? $provider->getProviderSlug() . $userData['id'] . '@localhost.com';
87 |
88 | $user = User::firstOrCreate(
89 | [
90 | 'email' => $email,
91 | ],
92 | [
93 | 'name' => $userData['first_name'] . ' ' .$userData['last_name'] ,
94 | 'password' => bcrypt(time().uniqid()),
95 | ]
96 | );
97 | }
98 |
99 | // Save user id
100 | $botAuth->user_id = $user->id;
101 | $botAuth->save();
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/Traits/BotAuthUserTrait.php:
--------------------------------------------------------------------------------
1 | hasMany(BotAuthModel::class);
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Translations/en/callback.php:
--------------------------------------------------------------------------------
1 | 'Code is not correct',
5 | 'return_back' => 'Return to the site',
6 | ];
--------------------------------------------------------------------------------