@ts-core/oauth 3.1.16 → 3.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +445 -1
- package/cjs/OAuthBase.d.ts +4 -2
- package/cjs/PopUpBase.d.ts +2 -2
- package/cjs/PopUpBase.js +5 -2
- package/cjs/external/cordovaInAppBrowserTgPlugin.d.ts +8 -0
- package/cjs/external/cordovaInAppBrowserTgPlugin.js +106 -0
- package/cjs/external/index.d.ts +1 -0
- package/cjs/external/index.js +1 -0
- package/cjs/keycloak/KeycloakAuth.d.ts +2 -0
- package/cjs/keycloak/KeycloakAuth.js +20 -0
- package/cjs/public-api.d.ts +1 -0
- package/cjs/public-api.js +1 -0
- package/cjs/tg/TgAuth.d.ts +2 -0
- package/cjs/tg/TgAuth.js +12 -0
- package/cjs/vk/VkAuth.js +1 -1
- package/esm/OAuthBase.d.ts +4 -2
- package/esm/PopUpBase.d.ts +2 -2
- package/esm/PopUpBase.js +5 -2
- package/esm/external/cordovaInAppBrowserTgPlugin.d.ts +8 -0
- package/esm/external/cordovaInAppBrowserTgPlugin.js +102 -0
- package/esm/external/index.d.ts +1 -0
- package/esm/external/index.js +1 -0
- package/esm/keycloak/KeycloakAuth.d.ts +2 -0
- package/esm/keycloak/KeycloakAuth.js +21 -1
- package/esm/public-api.d.ts +1 -0
- package/esm/public-api.js +1 -0
- package/esm/tg/TgAuth.d.ts +2 -0
- package/esm/tg/TgAuth.js +12 -0
- package/esm/vk/VkAuth.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,445 @@
|
|
|
1
|
-
|
|
1
|
+
# @ts-core/oauth
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ts-core/oauth)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://opensource.org/licenses/ISC)
|
|
6
|
+
|
|
7
|
+
TypeScript-библиотека для OAuth-аутентификации через различные провайдеры. Поддерживает браузеры, Cordova и Telegram Web Apps.
|
|
8
|
+
|
|
9
|
+
## Возможности
|
|
10
|
+
|
|
11
|
+
- **6 провайдеров**: Google, VK, Яндекс, Mail.ru, Keycloak, Telegram
|
|
12
|
+
- **3 платформы**: Browser (pop-up), Cordova (плагины), Telegram Web App
|
|
13
|
+
- **Унифицированный API**: единый интерфейс для всех провайдеров
|
|
14
|
+
- **TypeScript**: полная типизация, дженерики, интерфейсы
|
|
15
|
+
- **Минимум зависимостей**: только `@ts-core/common`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Установка
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @ts-core/oauth
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Быстрый старт
|
|
28
|
+
|
|
29
|
+
### Google OAuth
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { GoAuth, GoUser } from '@ts-core/oauth';
|
|
33
|
+
import { NullLogger } from '@ts-core/common';
|
|
34
|
+
|
|
35
|
+
const auth = new GoAuth(new NullLogger(), 'YOUR_CLIENT_ID');
|
|
36
|
+
|
|
37
|
+
// Получение токена через pop-up
|
|
38
|
+
const { codeOrToken } = await auth.getToken();
|
|
39
|
+
|
|
40
|
+
// Получение профиля пользователя
|
|
41
|
+
const user: GoUser = await auth.getProfile(codeOrToken);
|
|
42
|
+
console.log(user.name, user.email, user.picture);
|
|
43
|
+
|
|
44
|
+
// Не забудьте освободить ресурсы
|
|
45
|
+
auth.destroy();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### VK OAuth
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { VkAuth, VkUser } from '@ts-core/oauth';
|
|
52
|
+
import { NullLogger } from '@ts-core/common';
|
|
53
|
+
|
|
54
|
+
const auth = new VkAuth(new NullLogger(), 'YOUR_APP_ID');
|
|
55
|
+
|
|
56
|
+
// Получение кода авторизации
|
|
57
|
+
const { codeOrToken, redirectUri } = await auth.getCode();
|
|
58
|
+
|
|
59
|
+
// Обмен кода на токен (на сервере)
|
|
60
|
+
const token = await auth.getTokenByCode({ codeOrToken, redirectUri }, 'YOUR_SECRET');
|
|
61
|
+
|
|
62
|
+
// Получение профиля
|
|
63
|
+
const user: VkUser = await auth.getProfile(token.accessToken);
|
|
64
|
+
console.log(user.name, user.email, user.city);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Яндекс OAuth
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { YaAuth, YaUser } from '@ts-core/oauth';
|
|
71
|
+
import { NullLogger } from '@ts-core/common';
|
|
72
|
+
|
|
73
|
+
const auth = new YaAuth(new NullLogger(), 'YOUR_CLIENT_ID');
|
|
74
|
+
const { codeOrToken } = await auth.getToken();
|
|
75
|
+
const user: YaUser = await auth.getProfile(codeOrToken);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Mail.ru OAuth
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { MaAuth, MaUser } from '@ts-core/oauth';
|
|
82
|
+
import { NullLogger } from '@ts-core/common';
|
|
83
|
+
|
|
84
|
+
const auth = new MaAuth(new NullLogger(), 'YOUR_CLIENT_ID');
|
|
85
|
+
const { codeOrToken } = await auth.getToken();
|
|
86
|
+
const user: MaUser = await auth.getProfile(codeOrToken);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Keycloak OAuth
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { KeycloakAuth, KeycloakUser, IKeycloakAuthSettings } from '@ts-core/oauth';
|
|
93
|
+
import { NullLogger } from '@ts-core/common';
|
|
94
|
+
|
|
95
|
+
const settings: IKeycloakAuthSettings = {
|
|
96
|
+
url: 'https://keycloak.example.com',
|
|
97
|
+
realm: 'my-realm',
|
|
98
|
+
clientId: 'my-client'
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const auth = new KeycloakAuth(new NullLogger(), settings);
|
|
102
|
+
const { codeOrToken, redirectUri } = await auth.getCode();
|
|
103
|
+
const token = await auth.getTokenByCode({ codeOrToken, redirectUri }, 'YOUR_SECRET');
|
|
104
|
+
const user: KeycloakUser = await auth.getProfile(token.accessToken);
|
|
105
|
+
|
|
106
|
+
// Logout через pop-up окно
|
|
107
|
+
await auth.logout();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> **Logout**: метод `logout()` открывает pop-up с Keycloak logout endpoint. Keycloak завершает SSO-сессию и перенаправляет на redirect URI. Когда redirect-страница вызывает `window.close()`, промис резолвится.
|
|
111
|
+
|
|
112
|
+
### Telegram Web App
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { TgAuth, TgUser } from '@ts-core/oauth';
|
|
116
|
+
|
|
117
|
+
// Для Telegram Web Apps — данные из URL hash
|
|
118
|
+
const user: TgUser = TgAuth.getUser(window.location.hash);
|
|
119
|
+
console.log(user.id, user.name);
|
|
120
|
+
|
|
121
|
+
// Получение raw данных для верификации на сервере
|
|
122
|
+
const initData: string = TgAuth.getInitData(window.location.hash);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Telegram Login Widget
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { TgAuth, TgApiLoader } from '@ts-core/oauth';
|
|
129
|
+
import { NullLogger } from '@ts-core/common';
|
|
130
|
+
|
|
131
|
+
const api = new TgApiLoader(new NullLogger());
|
|
132
|
+
const auth = new TgAuth(new NullLogger(), { api, botId: YOUR_BOT_ID });
|
|
133
|
+
|
|
134
|
+
const user = await auth.getUser();
|
|
135
|
+
console.log(user.name, user.telegram);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Архитектура
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
144
|
+
│ PopUpBase<U> │
|
|
145
|
+
│ • Управление pop-up окном │
|
|
146
|
+
│ • Обработка событий postMessage │
|
|
147
|
+
│ • RxJS Observable для lifecycle событий │
|
|
148
|
+
└─────────────────────────────────────────────────────────────┘
|
|
149
|
+
│
|
|
150
|
+
▼
|
|
151
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
152
|
+
│ OAuthBase<T> │
|
|
153
|
+
│ • OAuth flow: getCode(), getToken() │
|
|
154
|
+
│ • Абстрактные методы: getProfile(), getTokenByCode() │
|
|
155
|
+
│ • HTTP клиент, state management │
|
|
156
|
+
└─────────────────────────────────────────────────────────────┘
|
|
157
|
+
│
|
|
158
|
+
┌───────────────────┼───────────────────┐
|
|
159
|
+
▼ ▼ ▼
|
|
160
|
+
┌─────────┐ ┌─────────┐ ┌───────────┐
|
|
161
|
+
│ GoAuth │ │ VkAuth │ │KeycloakAuth│
|
|
162
|
+
└─────────┘ └─────────┘ └───────────┘
|
|
163
|
+
│ │ │
|
|
164
|
+
▼ ▼ ▼
|
|
165
|
+
┌─────────┐ ┌─────────┐ ┌───────────┐
|
|
166
|
+
│ GoUser │ │ VkUser │ │KeycloakUser│
|
|
167
|
+
└─────────┘ └─────────┘ └───────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Провайдеры
|
|
171
|
+
|
|
172
|
+
| Провайдер | Auth класс | User класс | Особенности |
|
|
173
|
+
|-----------|------------|------------|-------------|
|
|
174
|
+
| Google | `GoAuth` | `GoUser` | People API для расширенных данных |
|
|
175
|
+
| VK | `VkAuth` | `VkUser` | Email из токена |
|
|
176
|
+
| Яндекс | `YaAuth` | `YaUser` | Стандартный OAuth 2.0 |
|
|
177
|
+
| Mail.ru | `MaAuth` | `MaUser` | Стандартный OAuth 2.0 |
|
|
178
|
+
| Keycloak | `KeycloakAuth` | `KeycloakUser` | Настраиваемый realm/URL, logout через pop-up |
|
|
179
|
+
| Telegram | `TgAuth` | `TgUser` | Web App + Login Widget |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
### OAuthBase<T>
|
|
186
|
+
|
|
187
|
+
Базовый класс для всех OAuth провайдеров.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
class OAuthBase<T> extends PopUpBase<IOAuthDto> {
|
|
191
|
+
constructor(logger: ILogger, applicationId: string, window?: Window);
|
|
192
|
+
|
|
193
|
+
// Основные методы
|
|
194
|
+
getCode(): Promise<IOAuthDto>; // OAuth flow с response_type=code
|
|
195
|
+
getToken(): Promise<IOAuthDto>; // OAuth flow с response_type=token
|
|
196
|
+
|
|
197
|
+
// Абстрактные методы (реализуются в провайдерах)
|
|
198
|
+
abstract getProfile(token: string): Promise<T>;
|
|
199
|
+
abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
200
|
+
abstract popUpUrl(): string;
|
|
201
|
+
|
|
202
|
+
// Настройки
|
|
203
|
+
redirectUri: string; // Кастомный redirect URI
|
|
204
|
+
popUpOpener: IPopUpOpener; // Функция открытия pop-up (browser/cordova)
|
|
205
|
+
|
|
206
|
+
// Свойства
|
|
207
|
+
readonly state: string; // Случайный state для CSRF защиты
|
|
208
|
+
readonly http: TransportHttp; // HTTP клиент
|
|
209
|
+
|
|
210
|
+
destroy(): void; // Освобождение ресурсов
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Тип функции открытия pop-up (поддерживает опциональный url для logout и др.)
|
|
214
|
+
type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### OAuthUser
|
|
219
|
+
|
|
220
|
+
Базовый класс пользователя с унифицированными полями.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
abstract class OAuthUser {
|
|
224
|
+
raw: any; // Оригинальный ответ API
|
|
225
|
+
|
|
226
|
+
id: string; // Уникальный ID пользователя
|
|
227
|
+
name: string; // Полное имя
|
|
228
|
+
email?: string; // Email
|
|
229
|
+
phone?: string; // Телефон
|
|
230
|
+
picture?: string; // URL аватара
|
|
231
|
+
nickname?: string; // Никнейм/username
|
|
232
|
+
|
|
233
|
+
isMale?: boolean; // Пол
|
|
234
|
+
birthday?: Date; // Дата рождения
|
|
235
|
+
|
|
236
|
+
city?: string; // Город
|
|
237
|
+
country?: string; // Страна
|
|
238
|
+
locale?: string; // Локаль
|
|
239
|
+
|
|
240
|
+
status?: string; // Статус
|
|
241
|
+
description?: string; // Описание/bio
|
|
242
|
+
|
|
243
|
+
// Социальные сети
|
|
244
|
+
vk?: string;
|
|
245
|
+
facebook?: string;
|
|
246
|
+
telegram?: string;
|
|
247
|
+
instagram?: string;
|
|
248
|
+
|
|
249
|
+
// Геттеры
|
|
250
|
+
readonly location: string; // "Country, City"
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### KeycloakAuth
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
class KeycloakAuth<T extends KeycloakUser> extends OAuthBase<T> {
|
|
258
|
+
constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
|
|
259
|
+
|
|
260
|
+
// Logout через pop-up окно
|
|
261
|
+
// Открывает Keycloak OIDC logout endpoint, ждёт закрытия окна
|
|
262
|
+
logout(): Promise<void>;
|
|
263
|
+
|
|
264
|
+
// Реализация OAuthBase
|
|
265
|
+
getProfile(token: string): Promise<T>;
|
|
266
|
+
getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
267
|
+
|
|
268
|
+
readonly settings: IKeycloakAuthSettings;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Интерфейсы
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Результат OAuth flow
|
|
276
|
+
interface IOAuthDto {
|
|
277
|
+
codeOrToken: string; // Код или токен
|
|
278
|
+
redirectUri: string; // Использованный redirect URI
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Токен авторизации
|
|
282
|
+
interface IOAuthToken {
|
|
283
|
+
accessToken: string;
|
|
284
|
+
expiresIn: number;
|
|
285
|
+
|
|
286
|
+
state?: string;
|
|
287
|
+
scope?: string;
|
|
288
|
+
userId?: number;
|
|
289
|
+
idToken?: string;
|
|
290
|
+
tokenType?: string;
|
|
291
|
+
refreshToken?: string;
|
|
292
|
+
refreshExpiresIn?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Настройки Keycloak
|
|
296
|
+
interface IKeycloakAuthSettings {
|
|
297
|
+
url: string; // URL сервера Keycloak
|
|
298
|
+
realm: string; // Название realm
|
|
299
|
+
clientId: string; // ID клиента
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Настройки Telegram
|
|
303
|
+
interface ITgAuthSettings {
|
|
304
|
+
api: ITgApiLoader; // Загрузчик Telegram API
|
|
305
|
+
botId: number; // ID бота
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Платформы
|
|
312
|
+
|
|
313
|
+
### Browser (по умолчанию)
|
|
314
|
+
|
|
315
|
+
Работает из коробки. Использует `window.open()` для pop-up и `postMessage` для коммуникации.
|
|
316
|
+
|
|
317
|
+
**Требования к redirect странице:**
|
|
318
|
+
|
|
319
|
+
```html
|
|
320
|
+
<!-- /oauth.html — обрабатывает и login callback, и logout redirect -->
|
|
321
|
+
<script>
|
|
322
|
+
const params = new URLSearchParams(window.location.search + window.location.hash.replace('#', '&'));
|
|
323
|
+
const code = params.get('code') || params.get('access_token');
|
|
324
|
+
if (code || params.get('error')) {
|
|
325
|
+
// Login callback — отправляем данные родительскому окну
|
|
326
|
+
window.opener.postMessage({
|
|
327
|
+
oAuthCodeOrToken: code,
|
|
328
|
+
oAuthError: params.get('error'),
|
|
329
|
+
oAuthErrorDescription: params.get('error_description')
|
|
330
|
+
}, '*');
|
|
331
|
+
}
|
|
332
|
+
// Закрываем pop-up (работает и для login, и для logout redirect)
|
|
333
|
+
window.close();
|
|
334
|
+
</script>
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Cordova OAuth Plugin
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { CordovaOAuthPluginPropertiesSet } from '@ts-core/oauth';
|
|
341
|
+
|
|
342
|
+
const auth = new GoAuth(logger, 'CLIENT_ID');
|
|
343
|
+
CordovaOAuthPluginPropertiesSet(auth);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Cordova InAppBrowser
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { CordovaInAppBrowserPluginPropertiesSet } from '@ts-core/oauth';
|
|
350
|
+
|
|
351
|
+
const auth = new GoAuth(logger, 'CLIENT_ID');
|
|
352
|
+
CordovaInAppBrowserPluginPropertiesSet(auth, async (url) => {
|
|
353
|
+
// Ваша логика получения токена по URL
|
|
354
|
+
return { codeOrToken: 'token', redirectUri: 'uri' };
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Расширенные возможности
|
|
361
|
+
|
|
362
|
+
### Google People API
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const auth = new GoAuth(logger, 'CLIENT_ID');
|
|
366
|
+
auth.personFields = 'genders,birthdays'; // Запросить дополнительные поля
|
|
367
|
+
|
|
368
|
+
const user = await auth.getProfile(token);
|
|
369
|
+
console.log(user.isMale, user.birthday);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Кастомные scopes
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const auth = new VkAuth(logger, 'APP_ID');
|
|
376
|
+
// Изменить scopes через params
|
|
377
|
+
auth.params.set('scope', 'friends,photos,email');
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Кастомный redirect URI
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
const auth = new GoAuth(logger, 'CLIENT_ID');
|
|
384
|
+
auth.redirectUri = 'https://myapp.com/callback';
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### События lifecycle
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
import { PopUpBaseEvent } from '@ts-core/oauth';
|
|
391
|
+
|
|
392
|
+
auth.events.subscribe(event => {
|
|
393
|
+
if (event.type === PopUpBaseEvent.OPENED) {
|
|
394
|
+
console.log('Pop-up opened');
|
|
395
|
+
}
|
|
396
|
+
if (event.type === PopUpBaseEvent.CLOSED) {
|
|
397
|
+
console.log('Pop-up closed');
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Структура проекта
|
|
405
|
+
|
|
406
|
+
```
|
|
407
|
+
src/
|
|
408
|
+
├── OAuthBase.ts # Базовый класс OAuth
|
|
409
|
+
├── OAuthUser.ts # Базовый класс пользователя
|
|
410
|
+
├── OAuthParser.ts # Парсер URL параметров
|
|
411
|
+
├── PopUpBase.ts # Управление pop-up окнами
|
|
412
|
+
├── public-api.ts # Единая точка экспорта
|
|
413
|
+
├── external/ # Платформенные интеграции
|
|
414
|
+
│ ├── browser.ts
|
|
415
|
+
│ ├── cordovaOAuthPlugin.ts
|
|
416
|
+
│ └── cordovaInAppBrowserPlugin.ts
|
|
417
|
+
├── go/ # Google
|
|
418
|
+
├── vk/ # VK
|
|
419
|
+
├── ya/ # Яндекс
|
|
420
|
+
├── ma/ # Mail.ru
|
|
421
|
+
├── keycloak/ # Keycloak
|
|
422
|
+
└── tg/ # Telegram
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Зависимости
|
|
428
|
+
|
|
429
|
+
| Пакет | Версия | Назначение |
|
|
430
|
+
|-------|--------|------------|
|
|
431
|
+
| `@ts-core/common` | ~3.0.43 | Logger, HTTP, утилиты |
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Лицензия
|
|
436
|
+
|
|
437
|
+
ISC License
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Автор
|
|
442
|
+
|
|
443
|
+
**Renat Gubaev** — [renat.gubaev@gmail.com](mailto:renat.gubaev@gmail.com)
|
|
444
|
+
|
|
445
|
+
GitHub: [ManhattanDoctor/ts-core-oauth](https://github.com/ManhattanDoctor/ts-core-oauth)
|
package/cjs/OAuthBase.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export declare abstract class OAuthBase<T = any> extends PopUpBase<IOAuthDto> {
|
|
|
8
8
|
constructor(logger: ILogger, applicationId: string, window?: Window);
|
|
9
9
|
protected getRedirectUri(): string;
|
|
10
10
|
protected getParams(): URLSearchParams;
|
|
11
|
-
protected parseMessageData(item:
|
|
12
|
-
protected isMessageError(item:
|
|
11
|
+
protected parseMessageData(item: IOAuthPopUpDto): IOAuthDto;
|
|
12
|
+
protected isMessageError(item: IOAuthPopUpDto): boolean;
|
|
13
13
|
protected parseMessageError(item: any): ExtendedError;
|
|
14
14
|
abstract getProfile(token: string, ...params: any[]): Promise<T>;
|
|
15
15
|
abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
@@ -24,6 +24,8 @@ export interface IOAuthDto {
|
|
|
24
24
|
codeOrToken: string;
|
|
25
25
|
redirectUri: string;
|
|
26
26
|
}
|
|
27
|
+
export interface IOAuthLogoutDto {
|
|
28
|
+
}
|
|
27
29
|
export interface IOAuthPopUpDto {
|
|
28
30
|
oAuthError?: string;
|
|
29
31
|
oAuthCodeOrToken?: string;
|
package/cjs/PopUpBase.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export declare abstract class PopUpBase<U> extends LoggerWrapper {
|
|
|
38
38
|
get closed(): Observable<Window>;
|
|
39
39
|
get opened(): Observable<Window>;
|
|
40
40
|
}
|
|
41
|
-
export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window): Window;
|
|
42
|
-
export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window) => Window;
|
|
41
|
+
export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string): Window;
|
|
42
|
+
export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
|
|
43
43
|
export type IPopUpMessageEventParser = (event: MessageEvent) => any;
|
|
44
44
|
export declare enum PopUpEvent {
|
|
45
45
|
OPENED = "OPENED",
|
package/cjs/PopUpBase.js
CHANGED
|
@@ -140,10 +140,13 @@ class PopUpBase extends common_1.LoggerWrapper {
|
|
|
140
140
|
}
|
|
141
141
|
exports.PopUpBase = PopUpBase;
|
|
142
142
|
PopUpBase.ERROR_WINDOW_CLOSED = 'WINDOW_CLOSED';
|
|
143
|
-
function popUpOpener(popUp, window) {
|
|
143
|
+
function popUpOpener(popUp, window, url) {
|
|
144
|
+
if (_.isNil(url)) {
|
|
145
|
+
url = popUp.popUpUrl();
|
|
146
|
+
}
|
|
144
147
|
let top = (window.screen.height - popUp.popUpHeight) / 2;
|
|
145
148
|
let left = (window.screen.width - popUp.popUpWidth) / 2;
|
|
146
|
-
return window.open(
|
|
149
|
+
return window.open(url, popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
|
|
147
150
|
}
|
|
148
151
|
exports.popUpOpener = popUpOpener;
|
|
149
152
|
var PopUpEvent;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TgAuth } from '../tg/TgAuth';
|
|
2
|
+
export declare const TgAuthCordovaInAppBrowserPluginPropertiesSet: (item: TgAuth, options: ITgAuthCordovaInAppBrowserOptions) => void;
|
|
3
|
+
export interface ITgAuthCordovaInAppBrowserOptions {
|
|
4
|
+
origin: string;
|
|
5
|
+
returnUrl: string;
|
|
6
|
+
requestAccess?: 'write';
|
|
7
|
+
inAppBrowserOptions?: string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TgAuthCordovaInAppBrowserPluginPropertiesSet = void 0;
|
|
4
|
+
const common_1 = require("@ts-core/common");
|
|
5
|
+
const PopUpBase_1 = require("../PopUpBase");
|
|
6
|
+
const TgUser_1 = require("../tg/TgUser");
|
|
7
|
+
const _ = require("lodash");
|
|
8
|
+
const TgAuthCordovaInAppBrowserPluginPropertiesSet = (item, options) => {
|
|
9
|
+
let returnUrl = options.returnUrl;
|
|
10
|
+
let origin = options.origin;
|
|
11
|
+
let requestAccess = options.requestAccess;
|
|
12
|
+
let inAppBrowserOptions = !_.isNil(options.inAppBrowserOptions) ? options.inAppBrowserOptions : 'location=no,clearcache=yes,clearsessioncache=yes';
|
|
13
|
+
item.userProvider = (botId) => new Promise((resolve, reject) => {
|
|
14
|
+
let cordova = window['cordova'];
|
|
15
|
+
if (_.isNil(cordova) || _.isNil(cordova.InAppBrowser)) {
|
|
16
|
+
reject(new common_1.ExtendedError(`Cordova InAppBrowser undefined, please check installed plugins`));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let url = `https://oauth.telegram.org/auth?bot_id=${botId}`
|
|
20
|
+
+ `&origin=${encodeURIComponent(origin)}`
|
|
21
|
+
+ `&return_to=${encodeURIComponent(returnUrl)}`;
|
|
22
|
+
if (!_.isNil(requestAccess)) {
|
|
23
|
+
url += `&request_access=${requestAccess}`;
|
|
24
|
+
}
|
|
25
|
+
let popUp = cordova.InAppBrowser.open(url, '_blank', inAppBrowserOptions);
|
|
26
|
+
let isSettled = false;
|
|
27
|
+
let cleanup = () => {
|
|
28
|
+
popUp.removeEventListener('loadstart', onLoad);
|
|
29
|
+
popUp.removeEventListener('loadstop', onLoad);
|
|
30
|
+
popUp.removeEventListener('exit', onExit);
|
|
31
|
+
};
|
|
32
|
+
let onLoad = (event) => {
|
|
33
|
+
if (isSettled || _.isNil(event) || _.isNil(event.url) || event.url.indexOf(returnUrl) !== 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let user = parseTgAuthResult(event.url);
|
|
37
|
+
if (_.isNil(user)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
isSettled = true;
|
|
41
|
+
cleanup();
|
|
42
|
+
popUp.close();
|
|
43
|
+
resolve(user);
|
|
44
|
+
};
|
|
45
|
+
let onExit = () => {
|
|
46
|
+
cleanup();
|
|
47
|
+
if (!isSettled) {
|
|
48
|
+
reject(new common_1.ExtendedError(PopUpBase_1.PopUpBase.ERROR_WINDOW_CLOSED, PopUpBase_1.PopUpBase.ERROR_WINDOW_CLOSED));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
popUp.addEventListener('loadstart', onLoad, false);
|
|
52
|
+
popUp.addEventListener('loadstop', onLoad, false);
|
|
53
|
+
popUp.addEventListener('exit', onExit, false);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
exports.TgAuthCordovaInAppBrowserPluginPropertiesSet = TgAuthCordovaInAppBrowserPluginPropertiesSet;
|
|
57
|
+
function parseTgAuthResult(url) {
|
|
58
|
+
let raw = null;
|
|
59
|
+
let hashIndex = url.indexOf('#');
|
|
60
|
+
if (hashIndex >= 0) {
|
|
61
|
+
let value = new URLSearchParams(url.substring(hashIndex + 1)).get('tgAuthResult');
|
|
62
|
+
if (!_.isEmpty(value)) {
|
|
63
|
+
try {
|
|
64
|
+
raw = base64UrlDecode(value);
|
|
65
|
+
}
|
|
66
|
+
catch (error) { }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let data = null;
|
|
70
|
+
if (!_.isNil(raw)) {
|
|
71
|
+
try {
|
|
72
|
+
data = JSON.parse(raw);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
let qIndex = url.indexOf('?');
|
|
80
|
+
if (qIndex < 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
let query = new URLSearchParams(url.substring(qIndex + 1, hashIndex >= 0 ? hashIndex : undefined));
|
|
84
|
+
if (_.isEmpty(query.get('hash'))) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
data = {};
|
|
88
|
+
query.forEach((v, k) => data[k] = v);
|
|
89
|
+
}
|
|
90
|
+
if (_.isNil(data) || _.isNil(data.hash)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
let user = new TgUser_1.TgUser();
|
|
94
|
+
user.parse(data);
|
|
95
|
+
return user;
|
|
96
|
+
}
|
|
97
|
+
function base64UrlDecode(value) {
|
|
98
|
+
let normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
99
|
+
let pad = normalized.length % 4;
|
|
100
|
+
if (pad !== 0) {
|
|
101
|
+
normalized += '='.repeat(4 - pad);
|
|
102
|
+
}
|
|
103
|
+
let binary = atob(normalized);
|
|
104
|
+
let bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
105
|
+
return new TextDecoder().decode(bytes);
|
|
106
|
+
}
|
package/cjs/external/index.d.ts
CHANGED
package/cjs/external/index.js
CHANGED
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./browser"), exports);
|
|
18
18
|
__exportStar(require("./cordovaOAuthPlugin"), exports);
|
|
19
19
|
__exportStar(require("./cordovaInAppBrowserPlugin"), exports);
|
|
20
|
+
__exportStar(require("./cordovaInAppBrowserTgPlugin"), exports);
|
|
@@ -5,9 +5,11 @@ export declare class KeycloakAuth<T extends KeycloakUser = KeycloakUser> extends
|
|
|
5
5
|
protected _settings: IKeycloakAuthSettings;
|
|
6
6
|
constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
|
|
7
7
|
protected getBaseUrl(): string;
|
|
8
|
+
protected getLogoutRedirectUri(): string;
|
|
8
9
|
popUpUrl(): string;
|
|
9
10
|
getProfile(token: string): Promise<T>;
|
|
10
11
|
getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
12
|
+
logout(): Promise<void>;
|
|
11
13
|
destroy(): void;
|
|
12
14
|
get settings(): IKeycloakAuthSettings;
|
|
13
15
|
}
|
|
@@ -25,6 +25,9 @@ class KeycloakAuth extends OAuthBase_1.OAuthBase {
|
|
|
25
25
|
getBaseUrl() {
|
|
26
26
|
return `${this.settings.url}/realms/${this.settings.realm}/protocol/openid-connect`;
|
|
27
27
|
}
|
|
28
|
+
getLogoutRedirectUri() {
|
|
29
|
+
return this.getRedirectUri();
|
|
30
|
+
}
|
|
28
31
|
popUpUrl() {
|
|
29
32
|
return `${this.getBaseUrl()}/auth?${this.getParams().toString()}`;
|
|
30
33
|
}
|
|
@@ -62,6 +65,23 @@ class KeycloakAuth extends OAuthBase_1.OAuthBase {
|
|
|
62
65
|
};
|
|
63
66
|
});
|
|
64
67
|
}
|
|
68
|
+
logout() {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
let params = new URLSearchParams();
|
|
71
|
+
params.append('client_id', this.applicationId);
|
|
72
|
+
params.append('post_logout_redirect_uri', this.getLogoutRedirectUri());
|
|
73
|
+
let url = `${this.getBaseUrl()}/logout?${params.toString()}`;
|
|
74
|
+
let popUp = this.popUpOpener(this, this.window, url);
|
|
75
|
+
return new Promise(resolve => {
|
|
76
|
+
let timer = setInterval(() => {
|
|
77
|
+
if (_.isNil(popUp) || popUp.closed) {
|
|
78
|
+
clearInterval(timer);
|
|
79
|
+
resolve();
|
|
80
|
+
}
|
|
81
|
+
}, common_1.DateUtil.MILLISECONDS_SECOND / 5);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
65
85
|
destroy() {
|
|
66
86
|
if (this.isDestroyed) {
|
|
67
87
|
return;
|
package/cjs/public-api.d.ts
CHANGED
package/cjs/public-api.js
CHANGED
|
@@ -34,3 +34,4 @@ __exportStar(require("./keycloak/KeycloakUser"), exports);
|
|
|
34
34
|
__exportStar(require("./external/browser"), exports);
|
|
35
35
|
__exportStar(require("./external/cordovaOAuthPlugin"), exports);
|
|
36
36
|
__exportStar(require("./external/cordovaInAppBrowserPlugin"), exports);
|
|
37
|
+
__exportStar(require("./external/cordovaInAppBrowserTgPlugin"), exports);
|
package/cjs/tg/TgAuth.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class TgAuth extends LoggerWrapper {
|
|
|
8
8
|
private static urlParseHashParams;
|
|
9
9
|
private static urlParseQueryString;
|
|
10
10
|
private static urlSafeDecode;
|
|
11
|
+
userProvider: ITgUserProvider;
|
|
11
12
|
protected promise: PromiseHandler<TgUser, ExtendedError>;
|
|
12
13
|
protected settings: ITgAuthSettings;
|
|
13
14
|
constructor(logger: ILogger, settings: ITgAuthSettings);
|
|
@@ -18,3 +19,4 @@ export interface ITgAuthSettings {
|
|
|
18
19
|
api: ITgApiLoader;
|
|
19
20
|
botId: number;
|
|
20
21
|
}
|
|
22
|
+
export type ITgUserProvider = (botId: number) => Promise<TgUser>;
|
package/cjs/tg/TgAuth.js
CHANGED
|
@@ -85,6 +85,18 @@ class TgAuth extends common_1.LoggerWrapper {
|
|
|
85
85
|
return this.promise.promise;
|
|
86
86
|
}
|
|
87
87
|
this.promise = common_1.PromiseHandler.create();
|
|
88
|
+
if (!_.isNil(this.userProvider)) {
|
|
89
|
+
this.userProvider(this.settings.botId)
|
|
90
|
+
.then(user => {
|
|
91
|
+
this.promise.resolve(user);
|
|
92
|
+
})
|
|
93
|
+
.catch(error => {
|
|
94
|
+
this.promise.reject(error instanceof common_1.ExtendedError ? error : new common_1.ExtendedError(error.message));
|
|
95
|
+
}).finally(() => {
|
|
96
|
+
this.promise = null;
|
|
97
|
+
});
|
|
98
|
+
return this.promise.promise;
|
|
99
|
+
}
|
|
88
100
|
this.settings.api.getApi()
|
|
89
101
|
.then(item => {
|
|
90
102
|
item.Login.auth({ bot_id: this.settings.botId }, item => {
|
package/cjs/vk/VkAuth.js
CHANGED
|
@@ -21,7 +21,7 @@ class VkAuth extends OAuthBase_1.OAuthBase {
|
|
|
21
21
|
this.params.set('scope', 'status,email');
|
|
22
22
|
}
|
|
23
23
|
popUpUrl() {
|
|
24
|
-
return `https://
|
|
24
|
+
return `https://id.vk.ru/authorize?${this.getParams().toString()}`;
|
|
25
25
|
}
|
|
26
26
|
getProfile(token, fields) {
|
|
27
27
|
return __awaiter(this, void 0, void 0, function* () {
|
package/esm/OAuthBase.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export declare abstract class OAuthBase<T = any> extends PopUpBase<IOAuthDto> {
|
|
|
8
8
|
constructor(logger: ILogger, applicationId: string, window?: Window);
|
|
9
9
|
protected getRedirectUri(): string;
|
|
10
10
|
protected getParams(): URLSearchParams;
|
|
11
|
-
protected parseMessageData(item:
|
|
12
|
-
protected isMessageError(item:
|
|
11
|
+
protected parseMessageData(item: IOAuthPopUpDto): IOAuthDto;
|
|
12
|
+
protected isMessageError(item: IOAuthPopUpDto): boolean;
|
|
13
13
|
protected parseMessageError(item: any): ExtendedError;
|
|
14
14
|
abstract getProfile(token: string, ...params: any[]): Promise<T>;
|
|
15
15
|
abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
@@ -24,6 +24,8 @@ export interface IOAuthDto {
|
|
|
24
24
|
codeOrToken: string;
|
|
25
25
|
redirectUri: string;
|
|
26
26
|
}
|
|
27
|
+
export interface IOAuthLogoutDto {
|
|
28
|
+
}
|
|
27
29
|
export interface IOAuthPopUpDto {
|
|
28
30
|
oAuthError?: string;
|
|
29
31
|
oAuthCodeOrToken?: string;
|
package/esm/PopUpBase.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export declare abstract class PopUpBase<U> extends LoggerWrapper {
|
|
|
38
38
|
get closed(): Observable<Window>;
|
|
39
39
|
get opened(): Observable<Window>;
|
|
40
40
|
}
|
|
41
|
-
export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window): Window;
|
|
42
|
-
export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window) => Window;
|
|
41
|
+
export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string): Window;
|
|
42
|
+
export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
|
|
43
43
|
export type IPopUpMessageEventParser = (event: MessageEvent) => any;
|
|
44
44
|
export declare enum PopUpEvent {
|
|
45
45
|
OPENED = "OPENED",
|
package/esm/PopUpBase.js
CHANGED
|
@@ -136,10 +136,13 @@ export class PopUpBase extends LoggerWrapper {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
PopUpBase.ERROR_WINDOW_CLOSED = 'WINDOW_CLOSED';
|
|
139
|
-
export function popUpOpener(popUp, window) {
|
|
139
|
+
export function popUpOpener(popUp, window, url) {
|
|
140
|
+
if (_.isNil(url)) {
|
|
141
|
+
url = popUp.popUpUrl();
|
|
142
|
+
}
|
|
140
143
|
let top = (window.screen.height - popUp.popUpHeight) / 2;
|
|
141
144
|
let left = (window.screen.width - popUp.popUpWidth) / 2;
|
|
142
|
-
return window.open(
|
|
145
|
+
return window.open(url, popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
|
|
143
146
|
}
|
|
144
147
|
export var PopUpEvent;
|
|
145
148
|
(function (PopUpEvent) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TgAuth } from '../tg/TgAuth';
|
|
2
|
+
export declare const TgAuthCordovaInAppBrowserPluginPropertiesSet: (item: TgAuth, options: ITgAuthCordovaInAppBrowserOptions) => void;
|
|
3
|
+
export interface ITgAuthCordovaInAppBrowserOptions {
|
|
4
|
+
origin: string;
|
|
5
|
+
returnUrl: string;
|
|
6
|
+
requestAccess?: 'write';
|
|
7
|
+
inAppBrowserOptions?: string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ExtendedError } from '@ts-core/common';
|
|
2
|
+
import { PopUpBase } from '../PopUpBase';
|
|
3
|
+
import { TgUser } from '../tg/TgUser';
|
|
4
|
+
import * as _ from 'lodash';
|
|
5
|
+
export const TgAuthCordovaInAppBrowserPluginPropertiesSet = (item, options) => {
|
|
6
|
+
let returnUrl = options.returnUrl;
|
|
7
|
+
let origin = options.origin;
|
|
8
|
+
let requestAccess = options.requestAccess;
|
|
9
|
+
let inAppBrowserOptions = !_.isNil(options.inAppBrowserOptions) ? options.inAppBrowserOptions : 'location=no,clearcache=yes,clearsessioncache=yes';
|
|
10
|
+
item.userProvider = (botId) => new Promise((resolve, reject) => {
|
|
11
|
+
let cordova = window['cordova'];
|
|
12
|
+
if (_.isNil(cordova) || _.isNil(cordova.InAppBrowser)) {
|
|
13
|
+
reject(new ExtendedError(`Cordova InAppBrowser undefined, please check installed plugins`));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let url = `https://oauth.telegram.org/auth?bot_id=${botId}`
|
|
17
|
+
+ `&origin=${encodeURIComponent(origin)}`
|
|
18
|
+
+ `&return_to=${encodeURIComponent(returnUrl)}`;
|
|
19
|
+
if (!_.isNil(requestAccess)) {
|
|
20
|
+
url += `&request_access=${requestAccess}`;
|
|
21
|
+
}
|
|
22
|
+
let popUp = cordova.InAppBrowser.open(url, '_blank', inAppBrowserOptions);
|
|
23
|
+
let isSettled = false;
|
|
24
|
+
let cleanup = () => {
|
|
25
|
+
popUp.removeEventListener('loadstart', onLoad);
|
|
26
|
+
popUp.removeEventListener('loadstop', onLoad);
|
|
27
|
+
popUp.removeEventListener('exit', onExit);
|
|
28
|
+
};
|
|
29
|
+
let onLoad = (event) => {
|
|
30
|
+
if (isSettled || _.isNil(event) || _.isNil(event.url) || event.url.indexOf(returnUrl) !== 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let user = parseTgAuthResult(event.url);
|
|
34
|
+
if (_.isNil(user)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
isSettled = true;
|
|
38
|
+
cleanup();
|
|
39
|
+
popUp.close();
|
|
40
|
+
resolve(user);
|
|
41
|
+
};
|
|
42
|
+
let onExit = () => {
|
|
43
|
+
cleanup();
|
|
44
|
+
if (!isSettled) {
|
|
45
|
+
reject(new ExtendedError(PopUpBase.ERROR_WINDOW_CLOSED, PopUpBase.ERROR_WINDOW_CLOSED));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
popUp.addEventListener('loadstart', onLoad, false);
|
|
49
|
+
popUp.addEventListener('loadstop', onLoad, false);
|
|
50
|
+
popUp.addEventListener('exit', onExit, false);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
function parseTgAuthResult(url) {
|
|
54
|
+
let raw = null;
|
|
55
|
+
let hashIndex = url.indexOf('#');
|
|
56
|
+
if (hashIndex >= 0) {
|
|
57
|
+
let value = new URLSearchParams(url.substring(hashIndex + 1)).get('tgAuthResult');
|
|
58
|
+
if (!_.isEmpty(value)) {
|
|
59
|
+
try {
|
|
60
|
+
raw = base64UrlDecode(value);
|
|
61
|
+
}
|
|
62
|
+
catch (error) { }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let data = null;
|
|
66
|
+
if (!_.isNil(raw)) {
|
|
67
|
+
try {
|
|
68
|
+
data = JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
let qIndex = url.indexOf('?');
|
|
76
|
+
if (qIndex < 0) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
let query = new URLSearchParams(url.substring(qIndex + 1, hashIndex >= 0 ? hashIndex : undefined));
|
|
80
|
+
if (_.isEmpty(query.get('hash'))) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
data = {};
|
|
84
|
+
query.forEach((v, k) => data[k] = v);
|
|
85
|
+
}
|
|
86
|
+
if (_.isNil(data) || _.isNil(data.hash)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
let user = new TgUser();
|
|
90
|
+
user.parse(data);
|
|
91
|
+
return user;
|
|
92
|
+
}
|
|
93
|
+
function base64UrlDecode(value) {
|
|
94
|
+
let normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
95
|
+
let pad = normalized.length % 4;
|
|
96
|
+
if (pad !== 0) {
|
|
97
|
+
normalized += '='.repeat(4 - pad);
|
|
98
|
+
}
|
|
99
|
+
let binary = atob(normalized);
|
|
100
|
+
let bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
101
|
+
return new TextDecoder().decode(bytes);
|
|
102
|
+
}
|
package/esm/external/index.d.ts
CHANGED
package/esm/external/index.js
CHANGED
|
@@ -5,9 +5,11 @@ export declare class KeycloakAuth<T extends KeycloakUser = KeycloakUser> extends
|
|
|
5
5
|
protected _settings: IKeycloakAuthSettings;
|
|
6
6
|
constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
|
|
7
7
|
protected getBaseUrl(): string;
|
|
8
|
+
protected getLogoutRedirectUri(): string;
|
|
8
9
|
popUpUrl(): string;
|
|
9
10
|
getProfile(token: string): Promise<T>;
|
|
10
11
|
getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
|
|
12
|
+
logout(): Promise<void>;
|
|
11
13
|
destroy(): void;
|
|
12
14
|
get settings(): IKeycloakAuthSettings;
|
|
13
15
|
}
|
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { ExtendedError, RandomUtil } from "@ts-core/common";
|
|
10
|
+
import { DateUtil, ExtendedError, RandomUtil } from "@ts-core/common";
|
|
11
11
|
import { OAuthBase } from "../OAuthBase";
|
|
12
12
|
import { KeycloakUser } from "./KeycloakUser";
|
|
13
13
|
import * as _ from 'lodash';
|
|
@@ -22,6 +22,9 @@ export class KeycloakAuth extends OAuthBase {
|
|
|
22
22
|
getBaseUrl() {
|
|
23
23
|
return `${this.settings.url}/realms/${this.settings.realm}/protocol/openid-connect`;
|
|
24
24
|
}
|
|
25
|
+
getLogoutRedirectUri() {
|
|
26
|
+
return this.getRedirectUri();
|
|
27
|
+
}
|
|
25
28
|
popUpUrl() {
|
|
26
29
|
return `${this.getBaseUrl()}/auth?${this.getParams().toString()}`;
|
|
27
30
|
}
|
|
@@ -59,6 +62,23 @@ export class KeycloakAuth extends OAuthBase {
|
|
|
59
62
|
};
|
|
60
63
|
});
|
|
61
64
|
}
|
|
65
|
+
logout() {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
let params = new URLSearchParams();
|
|
68
|
+
params.append('client_id', this.applicationId);
|
|
69
|
+
params.append('post_logout_redirect_uri', this.getLogoutRedirectUri());
|
|
70
|
+
let url = `${this.getBaseUrl()}/logout?${params.toString()}`;
|
|
71
|
+
let popUp = this.popUpOpener(this, this.window, url);
|
|
72
|
+
return new Promise(resolve => {
|
|
73
|
+
let timer = setInterval(() => {
|
|
74
|
+
if (_.isNil(popUp) || popUp.closed) {
|
|
75
|
+
clearInterval(timer);
|
|
76
|
+
resolve();
|
|
77
|
+
}
|
|
78
|
+
}, DateUtil.MILLISECONDS_SECOND / 5);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
62
82
|
destroy() {
|
|
63
83
|
if (this.isDestroyed) {
|
|
64
84
|
return;
|
package/esm/public-api.d.ts
CHANGED
package/esm/public-api.js
CHANGED
package/esm/tg/TgAuth.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class TgAuth extends LoggerWrapper {
|
|
|
8
8
|
private static urlParseHashParams;
|
|
9
9
|
private static urlParseQueryString;
|
|
10
10
|
private static urlSafeDecode;
|
|
11
|
+
userProvider: ITgUserProvider;
|
|
11
12
|
protected promise: PromiseHandler<TgUser, ExtendedError>;
|
|
12
13
|
protected settings: ITgAuthSettings;
|
|
13
14
|
constructor(logger: ILogger, settings: ITgAuthSettings);
|
|
@@ -18,3 +19,4 @@ export interface ITgAuthSettings {
|
|
|
18
19
|
api: ITgApiLoader;
|
|
19
20
|
botId: number;
|
|
20
21
|
}
|
|
22
|
+
export type ITgUserProvider = (botId: number) => Promise<TgUser>;
|
package/esm/tg/TgAuth.js
CHANGED
|
@@ -82,6 +82,18 @@ export class TgAuth extends LoggerWrapper {
|
|
|
82
82
|
return this.promise.promise;
|
|
83
83
|
}
|
|
84
84
|
this.promise = PromiseHandler.create();
|
|
85
|
+
if (!_.isNil(this.userProvider)) {
|
|
86
|
+
this.userProvider(this.settings.botId)
|
|
87
|
+
.then(user => {
|
|
88
|
+
this.promise.resolve(user);
|
|
89
|
+
})
|
|
90
|
+
.catch(error => {
|
|
91
|
+
this.promise.reject(error instanceof ExtendedError ? error : new ExtendedError(error.message));
|
|
92
|
+
}).finally(() => {
|
|
93
|
+
this.promise = null;
|
|
94
|
+
});
|
|
95
|
+
return this.promise.promise;
|
|
96
|
+
}
|
|
85
97
|
this.settings.api.getApi()
|
|
86
98
|
.then(item => {
|
|
87
99
|
item.Login.auth({ bot_id: this.settings.botId }, item => {
|
package/esm/vk/VkAuth.js
CHANGED
|
@@ -18,7 +18,7 @@ export class VkAuth extends OAuthBase {
|
|
|
18
18
|
this.params.set('scope', 'status,email');
|
|
19
19
|
}
|
|
20
20
|
popUpUrl() {
|
|
21
|
-
return `https://
|
|
21
|
+
return `https://id.vk.ru/authorize?${this.getParams().toString()}`;
|
|
22
22
|
}
|
|
23
23
|
getProfile(token, fields) {
|
|
24
24
|
return __awaiter(this, void 0, void 0, function* () {
|