@bmc-soft/keycloak-auth 1.0.0

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.
Files changed (3) hide show
  1. package/README.md +539 -0
  2. package/THEMING.md +116 -0
  3. package/package.json +98 -0
package/README.md ADDED
@@ -0,0 +1,539 @@
1
+ # @bmc-soft/keycloak-auth
2
+
3
+ Готовый к продакшену пакет аутентификации Keycloak для React Native с упором на производительность и безопасность.
4
+
5
+ ## Возможности
6
+
7
+ - **Производительность**: разбиение контекстов снижает лишние ре-рендеры; обновление токенов не затрагивает компоненты, которым нужны только конфиг или инстанс.
8
+ - **Безопасность**: токены в OS Keychain; PIN шифруется AES; учётные данные не хранятся в открытом виде.
9
+ - **Готовые экраны**: AuthPage, ConfirmAuthPage, виджеты ReauthBottomSheet и кнопки выхода.
10
+ - **Темизация**: настройка через тему в `KeycloakProvider` (цвета, шрифты, Loader, кнопки).
11
+ - **Автообновление токена**: обновление с повтором запроса; при ошибке — опциональный `onReauthRequired`.
12
+ - **Биометрия**: FaceID/TouchID для подтверждения по PIN.
13
+ - **React Native**: встроенные полифиллы для keycloak-js, без дополнительной настройки.
14
+
15
+ ## Установка
16
+
17
+ ```bash
18
+ npm install @bmc-soft/keycloak-auth
19
+ # или
20
+ yarn add @bmc-soft/keycloak-auth
21
+ ```
22
+
23
+ ### Peer-зависимости
24
+
25
+ ```bash
26
+ npm install react-native-keychain
27
+ ```
28
+
29
+ Настройка нативных модулей: [react-native-keychain](https://github.com/oblador/react-native-keychain).
30
+
31
+ Опционально (для интерцепторов): `axios` (>=1.0.0).
32
+ Для UI нужны: `@gorhom/bottom-sheet`, `react-native-webview`, `react-native-safe-area-context`, `lottie-react-native`, `react-native-svg`.
33
+
34
+ > Полифиллы для React Native подключаются автоматически при использовании пакета; отдельная настройка keycloak-js не требуется.
35
+
36
+ ---
37
+
38
+ ## Быстрый старт
39
+
40
+ ### 1. Оборачиваем приложение в KeycloakProvider
41
+
42
+ ```tsx
43
+ import { KeycloakProvider } from '@bmc-soft/keycloak-auth';
44
+
45
+ const App = () => (
46
+ <KeycloakProvider
47
+ config={{
48
+ url: 'https://your-keycloak-server.com',
49
+ realm: 'your-realm',
50
+ clientId: 'your-client-id',
51
+ }}
52
+ redirectUri="myapp://callback"
53
+ >
54
+ <YourApp />
55
+ </KeycloakProvider>
56
+ );
57
+ ```
58
+
59
+ ### 2. Экран входа
60
+
61
+ ```tsx
62
+ import { AuthPage } from '@bmc-soft/keycloak-auth';
63
+
64
+ const LoginScreen = () => (
65
+ <AuthPage
66
+ logo={require('./logo.png')}
67
+ onSuccess={(token) => {
68
+ // Сохраняем токен в сессию приложения и переходим
69
+ saveToken(token);
70
+ navigation.replace('Home');
71
+ }}
72
+ onError={(err) => console.error(err)}
73
+ />
74
+ );
75
+ ```
76
+
77
+ ### 3. (Опционально) Настройка интерцепторов Axios
78
+
79
+ Если используете axios, задайте провайдер токенов (см. [Axios](#axios)) и вызовите `setupAxiosInterceptors` с `tokenProvider` и `onReauthRequired`. Провайдер обычно настраивается внутри KeycloakProvider после инициализации keycloak (см. [Интеграция](#интеграция)).
80
+
81
+ ---
82
+
83
+ ## Провайдер и конфигурация
84
+
85
+ ### KeycloakProvider
86
+
87
+ | Проп | Тип | Обязательный | Описание |
88
+ |------|-----|--------------|----------|
89
+ | `children` | ReactNode | да | Дерево приложения |
90
+ | `config` | KeycloakConfigWith2FA | да | `url`, `realm`, `clientId`; опционально `clientId2fa` для первого входа с 2FA |
91
+ | `redirectUri` | string | да | URI OAuth callback (например `myapp://callback`) |
92
+ | `theme` | KeycloakTheme | нет | Цвета, шрифты, LoaderComponent, компоненты кнопок |
93
+ | `onTokens` | (tokens: KeycloakTokens) => void | нет | Вызывается при изменении токенов (логин, refresh, logout) |
94
+ | `autoRefreshToken` | boolean | нет | По умолчанию `true` |
95
+ | `autoRefreshTokenMinValidity` | number | нет | За сколько секунд до истечения вызывать refresh; по умолчанию `5` |
96
+ | `onReauthRequired` | () => void | нет | Вызывается при ошибке обновления токена (например показать экран реавторизации) |
97
+
98
+ ### KeycloakTheme
99
+
100
+ Все поля опциональны; недостающие подставляются из дефолтной темы. Передаётся в KeycloakProvider как `theme`.
101
+
102
+ - **fonts**: `primary`, `heading`
103
+ - **colors**: `primary`, `background`, `error`, `text`, `button`, `buttonText`, `link`, `outlinedButtonBackground`, `outlinedButtonText`, `numberPadButtonBackground`, `numberPadText`, `numberPadDisabled`, `pinIndicatorEmpty`, `border`, `success`
104
+ - **LoaderComponent**: компонент состояния загрузки
105
+ - **ContainedButtonComponent**, **OutlinedButtonComponent**, **IconButtonComponent**: компоненты кнопок (пропсы см. в THEMING.md)
106
+
107
+ Полная структура и примеры: [THEMING.md](./THEMING.md).
108
+
109
+ ---
110
+
111
+ ## Компоненты
112
+
113
+ ### Экраны
114
+
115
+ #### AuthPage
116
+
117
+ Первый вход: логин в WebView → установка PIN → обмен code на токены. Используется на экране «Вход».
118
+
119
+ | Проп | Тип | По умолчанию | Описание |
120
+ |------|-----|--------------|----------|
121
+ | `onSuccess` | (token: string) => void | — | После успешного обмена code; передаётся access token |
122
+ | `onError` | (error: Error) => void | — | При ошибке обмена или сохранения |
123
+ | `logo` | ReactNode \| ImageSourcePropType | — | Логотип приложения |
124
+ | `logoHeight`, `logoWidth` | number | 80 | Размер логотипа при использовании image source |
125
+ | `showBiometryPrompt` | boolean | true | Спрашивать биометрию после установки PIN |
126
+ | `pinLength` | number | 4 | Длина PIN |
127
+ | `style`, `paddingTop`, `paddingBottom` | ViewStyle / number | — | Стили контейнера |
128
+
129
+ #### ConfirmAuthPage
130
+
131
+ Подтверждение сессии: фазы webview_detect → pin → webview_with_credentials (если сохранены учётные данные). Используется на экране «Подтверждение PIN» и внутри BottomSheet реавторизации.
132
+
133
+ | Проп | Тип | По умолчанию | Описание |
134
+ |------|-----|--------------|----------|
135
+ | `onSuccess` | () => void | — | Успех (редирект или PIN без credentials) |
136
+ | `onError` | (error: Error) => void | — | Например неверный PIN |
137
+ | `onLogout` | () => void | — | После очистки хранилища пакета; в приложении — очистить сессию и перейти на экран входа |
138
+ | `logoutText` | string | "Выйти из аккаунта" | Текст ссылки «Выйти» |
139
+ | `logo`, `logoHeight`, `logoWidth` | — | 80 | Логотип |
140
+ | `pinLength` | number | 4 | Длина PIN |
141
+ | `allowBiometry`, `autoShowBiometry` | boolean | true | Биометрия |
142
+ | `title`, `description` | string | — | Заголовок и описание блока PIN |
143
+ | `style` | ViewStyle | — | Стиль контейнера |
144
+ | `webViewTimeoutMs` | number | 3000 | Задержка (мс) в фазе webview_detect перед переходом на PIN |
145
+
146
+ ### Виджеты
147
+
148
+ #### ReauthBottomSheet
149
+
150
+ BottomSheet с подтверждением PIN для реавторизации. Управляется только пропсами (не привязан к Session приложения).
151
+
152
+ | Проп | Тип | По умолчанию | Описание |
153
+ |------|-----|--------------|----------|
154
+ | `isVisible` | boolean | — | Отображать ли sheet |
155
+ | `onSuccess` | () => void | — | Реавторизация прошла успешно |
156
+ | `onDismiss` | () => void | — | Закрытие без успеха |
157
+ | `onError` | (error: Error) => void | — | Ошибка ввода PIN |
158
+ | `pinLength` | number | 4 | Длина PIN |
159
+ | `allowBiometry`, `autoShowBiometry` | boolean | true | Биометрия |
160
+ | `title` | string | "Подтвердите вход" | Заголовок |
161
+ | `description` | string | "Введите PIN-код" | Описание |
162
+ | `footer` | ReactNode | — | Опциональный футер |
163
+ | `snapPointPercentage` | number | 50 | Высота sheet в % |
164
+
165
+ ### Кнопки выхода
166
+
167
+ #### LogoutButtonText
168
+
169
+ Текстовая кнопка (contained или outlined). По нажатию открывается полноэкранный Modal с WebViewLogout. Должна использоваться внутри KeycloakProvider.
170
+
171
+ - **Базовые**: `onLogoutSuccess`, `onError`, `style`
172
+ - **Специфичные**: `variant: 'contained' | 'outlined'`, `label: string`
173
+
174
+ #### LogoutButtonIcon
175
+
176
+ Кнопка выхода только с иконкой. Базовые пропсы плюс `renderIcon: ReactNode`.
177
+
178
+ ### Остальные UI (внутри пакета или для кастомных сценариев)
179
+
180
+ - **WebViewLogin**, **WebViewLogout** — OAuth-потоки в WebView
181
+ - **PINSetup**, **PINConfirm** — ввод и подтверждение PIN
182
+ - **NumberPad**, **PINIndicator** — UI ввода PIN
183
+ - **LogoutConfirmSheet** — подтверждение выхода
184
+
185
+ ---
186
+
187
+ ## Хуки
188
+
189
+ Все хуки должны вызываться внутри KeycloakProvider (если не указано иное).
190
+
191
+ ### useKeycloakAuth()
192
+
193
+ Полный API авторизации: инстанс keycloak, токен, login, logout, URL, состояние реавторизации.
194
+
195
+ Возвращает: `keycloak`, `isInitialized`, `isLoading`, `error`, `token`, `isExpired`, `isAuthenticated`, `updateToken`, `login`, `logout`, `loadUserProfile`, `createLoginUrl`, `createLogoutUrl`, `clearTokens`, `isReauthRequired`, `showReauth`, `hideReauth`.
196
+
197
+ ### useToken()
198
+
199
+ Доступ только к токенам (меньше ре-рендеров). Возвращает: `token`, `refreshToken`, `idToken`, `isExpired`, `updateToken`, `clearTokens`.
200
+
201
+ ### useReauth()
202
+
203
+ Состояние UI реавторизации. Возвращает: `isReauthRequired`, `showReauth`, `hideReauth`.
204
+
205
+ ### useKeycloakAuthScreen(options?)
206
+
207
+ Определяет, какой экран авторизации показывать: `'login'` или `'confirm'`. Используется для `initialRouteName` в Auth Stack.
208
+
209
+ Возвращает: `screen: 'login' | 'confirm' | null`, `isLoading`, `error`.
210
+
211
+ Опции: `shouldShowConfirm?: () => Promise<boolean>` — переопределить стандартную проверку (наличие токенов и PIN).
212
+
213
+ ### useKeycloakTheme()
214
+
215
+ Текущая тема (объединённая с дефолтной). Возвращает: `fonts`, `colors`, `LoaderComponent`, `ContainedButtonComponent`, `OutlinedButtonComponent`, `IconButtonComponent`. Можно вызывать вне провайдера (вернётся дефолтная тема).
216
+
217
+ ---
218
+
219
+ ## Хранилище
220
+
221
+ ### tokenStorage
222
+
223
+ Хранение токенов в Keychain. API: `getToken`, `saveToken`, `getRefreshToken`, `saveRefreshToken`, `getIdToken`, `saveIdToken`, `getTokens`, `saveTokens`, `clearTokens`, `hasTokens`.
224
+
225
+ После успешного ConfirmAuthPage (например при реавторизации) вызовите `tokenStorage.getToken()`, чтобы получить текущий access token и обновить сессию приложения.
226
+
227
+ > **Важно**: не храните токены в AsyncStorage. Используйте только Keychain через пакет.
228
+
229
+ ### credentialStorage
230
+
231
+ Используется внутри пакета для зашифрованных учётных данных и биометрии. Экспортируется для продвинутых сценариев (например очистка PIN/credentials при выходе).
232
+
233
+ ---
234
+
235
+ ## Axios
236
+
237
+ ### Интерфейс TokenProvider
238
+
239
+ Используется интерцепторами для получения и обновления токенов:
240
+
241
+ - `getToken(): Promise<string | null> | string | null`
242
+ - `refreshToken(): Promise<string | null>`
243
+ - `hasRefreshToken?(): Promise<boolean> | boolean` (опционально)
244
+ - `formatToken?(token: string): string` (опционально; по умолчанию `Bearer ${token}`)
245
+
246
+ ### setupAxiosInterceptors(axiosInstance, config)
247
+
248
+ Добавляет интерцепторы: в запрос — подстановка токена; в ответ — retry при 401, обновление токена, при ошибке — вызов `onReauthRequired`.
249
+
250
+ **config**: `tokenProvider`, `onReauthRequired`, `onTokenRefreshed`, `onRefreshError`, `maxRetries` (по умолчанию 1), `autoAddToken` (true), `autoRetryOn401` (true), `excludeEndpoints`.
251
+
252
+ Возвращает `{ cleanup }` для снятия интерцепторов.
253
+
254
+ ### KeycloakTokenProvider
255
+
256
+ Класс, реализующий TokenProvider на основе инстанса KeycloakReactNativeClient. Удобно, когда инстанс клиента уже есть и нужен адаптер для интерцепторов.
257
+
258
+ ---
259
+
260
+ ## Интеграция
261
+
262
+ Пошаговая интеграция с примерами кода. Рассматривается только Keycloak.
263
+
264
+ ### 1. Оборачивание приложения в KeycloakProvider
265
+
266
+ Конфиг и `redirectUri` берут из настроек приложения. Тему (Loader, кнопки, цвета) передайте из темы приложения. В `onTokens` сохраняйте токен в хранилище сессии; в `onReauthRequired` показывайте экран реавторизации.
267
+
268
+ **Порядок провайдеров**: если используется `BottomSheetModalProvider` из `@gorhom/bottom-sheet` (например для реавторизации или других модалок), он должен находиться **внутри** KeycloakProvider. Иначе ReauthBottomSheet или ConfirmAuthPage внутри модалки не получат контекст Keycloak. Порядок: `KeycloakProvider` → `BottomSheetModalProvider` → остальное дерево приложения.
269
+
270
+ ```tsx
271
+ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
272
+ import { KeycloakProvider, type KeycloakTheme } from '@bmc-soft/keycloak-auth';
273
+
274
+ const theme: KeycloakTheme = {
275
+ colors: {
276
+ primary: appColors.primary,
277
+ background: appColors.background,
278
+ error: appColors.error,
279
+ text: appColors.text,
280
+ numberPadButtonBackground: appColors.cardBackground,
281
+ numberPadText: appColors.text,
282
+ pinIndicatorEmpty: appColors.textMuted,
283
+ },
284
+ LoaderComponent: AppLoader,
285
+ ContainedButtonComponent: AppContainedButton,
286
+ OutlinedButtonComponent: AppOutlinedButton,
287
+ IconButtonComponent: AppIconButton,
288
+ };
289
+
290
+ export const App = () => (
291
+ <KeycloakProvider
292
+ config={{
293
+ url: 'https://sso.example.com',
294
+ realm: 'my-realm',
295
+ clientId: 'my-app',
296
+ }}
297
+ redirectUri="myapp://callback"
298
+ theme={theme}
299
+ onTokens={({ token }) => {
300
+ Session.events.onChangeAuthToken(token);
301
+ }}
302
+ onReauthRequired={() => {
303
+ Session.events.onRequireReauth();
304
+ }}
305
+ >
306
+ <BottomSheetModalProvider>
307
+ <RootNavigator />
308
+ </BottomSheetModalProvider>
309
+ </KeycloakProvider>
310
+ );
311
+ ```
312
+
313
+ ### 2. Установка TokenProvider для axios
314
+
315
+ Провайдер токенов настраивается один раз внутри KeycloakProvider после инициализации keycloak (например во вспомогательном компоненте). Не дублируйте настройку на экране логина.
316
+
317
+ ```tsx
318
+ import { useKeycloakAuth } from '@bmc-soft/keycloak-auth';
319
+ import { setKeycloakTokenProvider, resetKeycloakTokenProvider } from './axios';
320
+
321
+ const KeycloakAxiosTokenProviderSetup = ({ children }) => {
322
+ const { keycloak, isInitialized } = useKeycloakAuth();
323
+
324
+ useEffect(() => {
325
+ if (isInitialized && keycloak) {
326
+ setKeycloakTokenProvider({
327
+ getToken: () => keycloak.token || null,
328
+ updateToken: async (minValidity) => {
329
+ const result = await keycloak.updateToken(minValidity);
330
+ return result !== null;
331
+ },
332
+ });
333
+ }
334
+ return () => resetKeycloakTokenProvider();
335
+ }, [isInitialized, keycloak]);
336
+
337
+ return <>{children}</>;
338
+ };
339
+
340
+ // Внутри KeycloakProvider:
341
+ <KeycloakProvider config={...} redirectUri={...} ...>
342
+ <KeycloakAxiosTokenProviderSetup>
343
+ <BottomSheetModalProvider>
344
+ <RootNavigator />
345
+ </BottomSheetModalProvider>
346
+ </KeycloakAxiosTokenProviderSetup>
347
+ </KeycloakProvider>
348
+ ```
349
+
350
+ Подключение интерцепторов к инстансу axios (там, где он создаётся):
351
+
352
+ ```tsx
353
+ import { setupAxiosInterceptors } from '@bmc-soft/keycloak-auth';
354
+ import { getKeycloakTokenProvider } from './keycloakTokenProvider';
355
+
356
+ const api = axios.create({ baseURL: 'https://api.example.com' });
357
+
358
+ setupAxiosInterceptors(api, {
359
+ tokenProvider: {
360
+ getToken: () => getKeycloakTokenProvider()?.getToken() ?? null,
361
+ refreshToken: () =>
362
+ getKeycloakTokenProvider()
363
+ ?.updateToken(120)
364
+ .then((ok) => (ok ? getKeycloakTokenProvider()?.getToken() ?? null : null)),
365
+ },
366
+ onReauthRequired: () => Session.events.onRequireReauth(),
367
+ });
368
+ ```
369
+
370
+ ### 3. Стек авторизации (Login + Confirm)
371
+
372
+ Два экрана: Login (AuthPage) и Confirm (ConfirmAuthPage). Начальный маршрут задаётся через `useKeycloakAuthScreen()`: показывать Confirm, если есть токены и PIN, иначе Login.
373
+
374
+ ```tsx
375
+ import { useKeycloakAuthScreen, AuthPage, ConfirmAuthPage, tokenStorage } from '@bmc-soft/keycloak-auth';
376
+
377
+ const Stack = createNativeStackNavigator();
378
+
379
+ export const AuthStack = () => {
380
+ const { screen, isLoading } = useKeycloakAuthScreen();
381
+
382
+ if (isLoading) return <Loader />;
383
+
384
+ const initialRoute = screen === 'confirm' ? 'Confirm' : 'Login';
385
+
386
+ return (
387
+ <Stack.Navigator initialRouteName={initialRoute}>
388
+ <Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />
389
+ <Stack.Screen name="Confirm" component={ConfirmScreen} options={{ headerShown: false }} />
390
+ </Stack.Navigator>
391
+ );
392
+ };
393
+
394
+ const LoginScreen = () => (
395
+ <AuthPage
396
+ logo={require('./logo.png')}
397
+ onSuccess={(token) => Session.events.onLogin(token)}
398
+ onError={(err) => console.error(err)}
399
+ />
400
+ );
401
+
402
+ const ConfirmScreen = () => {
403
+ const navigation = useNavigation();
404
+
405
+ const onSuccess = useCallback(async () => {
406
+ const token = await tokenStorage.getToken();
407
+ if (token) Session.events.onLogin(token);
408
+ }, []);
409
+
410
+ return (
411
+ <ConfirmAuthPage
412
+ onSuccess={onSuccess}
413
+ onLogout={() => {
414
+ Session.events.onLogout();
415
+ navigation.replace('Login');
416
+ }}
417
+ pinLength={4}
418
+ />
419
+ );
420
+ };
421
+ ```
422
+
423
+ ### 4. Реавторизация при 401
424
+
425
+ При 401 интерцепторы вызывают `onReauthRequired`. Покажите BottomSheet (или полноэкранный экран) с ConfirmAuthPage. После успешного ввода PIN получите токен из пакета, обновите сессию и закройте реавторизацию.
426
+
427
+ ```tsx
428
+ import { ConfirmAuthPage, tokenStorage } from '@bmc-soft/keycloak-auth';
429
+ import BottomSheet from '@gorhom/bottom-sheet';
430
+
431
+ export const ReauthBottomSheet = () => {
432
+ const sheetRef = useRef(null);
433
+ const showReauth = useStore($reauthRequired); // например Effector / useState
434
+
435
+ useEffect(() => {
436
+ showReauth ? sheetRef.current?.snapToIndex(0) : sheetRef.current?.close();
437
+ }, [showReauth]);
438
+
439
+ const handleSuccess = useCallback(async () => {
440
+ const token = await tokenStorage.getToken();
441
+ if (token) {
442
+ Session.events.onLogin(token);
443
+ Session.events.onReauthCompleted();
444
+ }
445
+ sheetRef.current?.close();
446
+ }, []);
447
+
448
+ return (
449
+ <BottomSheet ref={sheetRef} snapPoints={['50%']} enablePanDownToClose>
450
+ <ConfirmAuthPage onSuccess={handleSuccess} pinLength={4} />
451
+ </BottomSheet>
452
+ );
453
+ };
454
+ ```
455
+
456
+ ### 5. Выход
457
+
458
+ Используйте LogoutButtonIcon или LogoutButtonText. По нажатию открывается Modal с WebViewLogout; при успешном выходе вызывается `onLogoutSuccess` — там очищайте сессию приложения.
459
+
460
+ ```tsx
461
+ import { LogoutButtonIcon, LogoutButtonText } from '@bmc-soft/keycloak-auth';
462
+
463
+ export const LogoutButton = ({ variant }: { variant: 'icon' | 'text' }) => {
464
+ const handleLogoutSuccess = useCallback(() => {
465
+ Session.events.onLogout();
466
+ }, []);
467
+
468
+ if (variant === 'icon') {
469
+ return (
470
+ <LogoutButtonIcon
471
+ renderIcon={<Icon name="logout" color={theme.error} />}
472
+ onLogoutSuccess={handleLogoutSuccess}
473
+ />
474
+ );
475
+ }
476
+
477
+ return (
478
+ <LogoutButtonText
479
+ variant="outlined"
480
+ label="Выйти"
481
+ onLogoutSuccess={handleLogoutSuccess}
482
+ />
483
+ );
484
+ };
485
+ ```
486
+
487
+ ### Схема потока
488
+
489
+ ```
490
+ KeycloakProvider → инициализация Keycloak → установка TokenProvider для axios
491
+
492
+ useKeycloakAuthScreen → Login (AuthPage) или Confirm (ConfirmAuthPage)
493
+
494
+ onSuccess → Session.onLogin(token) | onLogout → Session.onLogout + навигация
495
+
496
+ axios 401 → onReauthRequired → ReauthBottomSheet с ConfirmAuthPage
497
+
498
+ onSuccess → tokenStorage.getToken() → Session.onLogin + onReauthCompleted → закрыть sheet
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Архитектура и безопасность
504
+
505
+ ### Иерархия контекстов
506
+
507
+ ```
508
+ KeycloakConfigProvider ← конфиг (редко меняется)
509
+ └─ KeycloakInstanceProvider ← инстанс (один раз)
510
+ └─ KeycloakThemeProvider ← тема (опционально; мемоизирована)
511
+ └─ TokenProvider ← токены (частые обновления)
512
+ └─ ReauthProvider ← состояние реавторизации
513
+ ```
514
+
515
+ Обновление токенов не вызывает ре-рендер компонентов, зависящих только от конфига, инстанса или темы.
516
+
517
+ ### Безопасность
518
+
519
+ - Токены в OS Keychain (не AsyncStorage)
520
+ - PIN шифруется AES перед сохранением
521
+ - Запросы по HTTPS
522
+ - Учётные данные не хранятся в открытом виде
523
+
524
+ ### Полифиллы React Native
525
+
526
+ Пакет подключает полифиллы для `document`, `window`, `navigator`, требуемые keycloak-js. Они применяются автоматически при использовании KeycloakProvider. Чтобы применить раньше: `import { initKeycloakPolyfills } from '@bmc-soft/keycloak-auth'; initKeycloakPolyfills();`.
527
+
528
+ ---
529
+
530
+ ## Экспорты
531
+
532
+ - **Основной**: `@bmc-soft/keycloak-auth` — провайдер, хуки, экраны, виджеты, UI, хранилище, помощники axios, типы
533
+ - **Подпути**: `@bmc-soft/keycloak-auth/screens`, `/widgets`, `/hooks`, `/axios`, `/context`, `/storage` (см. `exports` в package.json)
534
+
535
+ ---
536
+
537
+ ## Лицензия
538
+
539
+ MIT © Koala Team
package/THEMING.md ADDED
@@ -0,0 +1,116 @@
1
+ # Theming
2
+
3
+ Настройка внешнего вида экранов и виджетов Keycloak через единую тему в `KeycloakProvider`.
4
+
5
+ ## Передача темы
6
+
7
+ Передайте объект `theme` в `KeycloakProvider`. Все поля опциональны; не указанные значения берутся из дефолтной темы пакета.
8
+
9
+ ```tsx
10
+ import { KeycloakProvider, type KeycloakTheme } from '@bmc-soft/keycloak-auth';
11
+
12
+ const theme: KeycloakTheme = {
13
+ fonts: { primary: 'OpenSans-Regular', heading: 'OpenSans-SemiBold' },
14
+ colors: { background: '#0d0d0d', primary: '#0066cc' },
15
+ LoaderComponent: MyLoader,
16
+ };
17
+
18
+ <KeycloakProvider config={config} redirectUri={redirectUri} theme={theme}>
19
+ <App />
20
+ </KeycloakProvider>
21
+ ```
22
+
23
+ ## Приоритет значений
24
+
25
+ Во всех компонентах пакета действует правило:
26
+
27
+ **проп компонента > тема из контекста > дефолт пакета**
28
+
29
+ Например, в теме задаётся `colors.background` — экраны и виджеты пакета (AuthPage, ConfirmAuthPage, ReauthBottomSheet и др.) используют его для фона. Компоненты, у которых есть соответствующие пропы (например `style`), могут переопределять стили точечно.
30
+
31
+ ## Структура KeycloakTheme
32
+
33
+ Тип экспортируется из пакета: `import type { KeycloakTheme } from '@bmc-soft/keycloak-auth'`.
34
+
35
+ | Поле | Тип | Описание |
36
+ |------|-----|----------|
37
+ | `fonts` | `KeycloakThemeFonts` | Шрифты для текста и заголовков |
38
+ | `colors` | `KeycloakThemeColors` | Цвета экранов, кнопок, ошибок и т.д. |
39
+ | `LoaderComponent` | `React.ComponentType` | Компонент индикатора загрузки |
40
+ | `ContainedButtonComponent` | `React.ComponentType<KeycloakThemeButtonProps>` | Кнопка с заливкой |
41
+ | `OutlinedButtonComponent` | `React.ComponentType<KeycloakThemeButtonProps>` | Кнопка с обводкой |
42
+ | `IconButtonComponent` | `React.ComponentType<KeycloakThemeIconButtonProps>` | Иконковая кнопка |
43
+
44
+ ### KeycloakThemeFonts
45
+
46
+ - `primary?: string` — основной шрифт (текст)
47
+ - `heading?: string` — шрифт заголовков
48
+
49
+ ### KeycloakThemeColors
50
+
51
+ Все поля опциональны. Основные:
52
+
53
+ - `primary` — акцент (ссылки, активный PIN)
54
+ - `background` — фон экранов и контейнеров
55
+ - `error` — ошибки и опасные действия
56
+ - `text` — основной текст
57
+ - `button` / `buttonText` — кнопка с заливкой
58
+ - `link` — ссылки
59
+ - `outlinedButtonBackground` / `outlinedButtonText` — кнопка с обводкой
60
+ - `numberPadButtonBackground` / `numberPadText` / `numberPadDisabled` — клавиатура PIN
61
+ - `pinIndicatorEmpty` — пустые точки PIN-индикатора
62
+ - `border` — разделители
63
+ - `success` — успешное состояние
64
+
65
+ ## Пример с приложением
66
+
67
+ Подстановка темы приложения (например, из shared/settings или brandbook):
68
+
69
+ ```tsx
70
+ import { KeycloakProvider, type KeycloakTheme } from '@bmc-soft/keycloak-auth';
71
+ import { useAppTheme } from '@shared/settings';
72
+ import { Loader } from '@shared/ui';
73
+
74
+ function KeycloakAppProvider({ children }) {
75
+ const { colors, fonts } = useAppTheme();
76
+
77
+ const theme: KeycloakTheme = {
78
+ fonts: {
79
+ primary: fonts?.regular,
80
+ heading: fonts?.semiBold,
81
+ },
82
+ colors: {
83
+ primary: colors.primary,
84
+ background: colors.screenBackground,
85
+ error: colors.error,
86
+ text: colors.text,
87
+ },
88
+ LoaderComponent: Loader,
89
+ };
90
+
91
+ return (
92
+ <KeycloakProvider config={config} redirectUri={redirectUri} theme={theme}>
93
+ {children}
94
+ </KeycloakProvider>
95
+ );
96
+ }
97
+ ```
98
+
99
+ ## Хук useKeycloakTheme()
100
+
101
+ Компоненты пакета получают тему через внутренний контекст. Если вы пишете обёртку или кастомный экран, можно использовать хук:
102
+
103
+ ```tsx
104
+ import { useKeycloakTheme } from '@bmc-soft/keycloak-auth';
105
+
106
+ function MyWrapper() {
107
+ const { colors, LoaderComponent } = useKeycloakTheme();
108
+ return (
109
+ <View style={{ backgroundColor: colors.background }}>
110
+ <LoaderComponent />
111
+ </View>
112
+ );
113
+ }
114
+ ```
115
+
116
+ Без обёртки `KeycloakProvider` хук возвращает дефолтную тему пакета (не бросает ошибку).
package/package.json ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "name": "@bmc-soft/keycloak-auth",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready Keycloak authentication package for React Native with optimized performance and security",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "THEMING.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./screens": {
18
+ "types": "./dist/screens/index.d.ts",
19
+ "default": "./dist/screens/index.js"
20
+ },
21
+ "./widgets": {
22
+ "types": "./dist/widgets/index.d.ts",
23
+ "default": "./dist/widgets/index.js"
24
+ },
25
+ "./hooks": {
26
+ "types": "./dist/hooks/index.d.ts",
27
+ "default": "./dist/hooks/index.js"
28
+ },
29
+ "./axios": {
30
+ "types": "./dist/axios/index.d.ts",
31
+ "default": "./dist/axios/index.js"
32
+ },
33
+ "./context": {
34
+ "types": "./dist/context/index.d.ts",
35
+ "default": "./dist/context/index.js"
36
+ },
37
+ "./storage": {
38
+ "types": "./dist/storage/index.d.ts",
39
+ "default": "./dist/storage/index.js"
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "watch": "tsc --watch",
45
+ "clean": "rm -rf dist",
46
+ "prepublishOnly": "npm run clean && npm run build",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "keywords": [
50
+ "bmc-soft"
51
+ ],
52
+ "author": "BMC-Soft Team",
53
+ "license": "MIT",
54
+ "peerDependencies": {
55
+ "@gorhom/bottom-sheet": ">=5.0.0",
56
+ "axios": ">=1.0.0",
57
+ "react": ">=18.0.0",
58
+ "react-native": ">=0.72.0",
59
+ "react-native-safe-area-context": ">=4.0.0",
60
+ "react-native-webview": ">=13.0.0",
61
+ "lottie-react-native": ">=6.0.0",
62
+ "react-native-svg": ">=14.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "axios": {
66
+ "optional": true
67
+ }
68
+ },
69
+ "dependencies": {
70
+ "@react-keycloak/keycloak-ts": "^0.2.4",
71
+ "@types/crypto-js": "^4.2.0",
72
+ "crypto-js": "^4.2.0",
73
+ "react-native-get-random-values": "^1.11.0",
74
+ "react-native-keychain": "^8.1.0"
75
+ },
76
+ "devDependencies": {
77
+ "@gorhom/bottom-sheet": ">=5.0.0",
78
+ "@types/react": "^18.0.0",
79
+ "@types/react-native": "^0.72.0",
80
+ "axios": ">=1.0.0",
81
+ "lottie-react-native": ">=6.0.0",
82
+ "react": ">=18.0.0",
83
+ "react-native": ">=0.72.0",
84
+ "react-native-safe-area-context": ">=4.0.0",
85
+ "react-native-svg": ">=14.0.0",
86
+ "react-native-webview": ">=13.0.0",
87
+ "typescript": "^5.3.0"
88
+ },
89
+ "sideEffects": false,
90
+ "repository": {
91
+ "type": "git",
92
+ "url": "https://github.com/waohwaohwaoh/keycloak_auth.git"
93
+ },
94
+ "homepage": "https://github.com/waohwaohwaoh/keycloak_auth#readme",
95
+ "bugs": {
96
+ "url": "https://github.com/waohwaohwaoh/keycloak_auth/issues"
97
+ }
98
+ }