@skylabs-digital/react-identity-access 2.32.0 → 3.1.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.
- package/README.md +439 -352
- package/dist/components/LoginForm.d.ts +2 -19
- package/dist/components/LoginForm.d.ts.map +1 -1
- package/dist/components/MagicLinkForm.d.ts +2 -23
- package/dist/components/MagicLinkForm.d.ts.map +1 -1
- package/dist/components/PasswordRecoveryForm.d.ts +2 -19
- package/dist/components/PasswordRecoveryForm.d.ts.map +1 -1
- package/dist/components/SignupForm.d.ts +2 -21
- package/dist/components/SignupForm.d.ts.map +1 -1
- package/dist/components/authFormShared.d.ts +39 -0
- package/dist/components/authFormShared.d.ts.map +1 -0
- package/dist/errors/SessionErrors.d.ts +10 -0
- package/dist/errors/SessionErrors.d.ts.map +1 -1
- package/dist/hooks/useAuthForm.d.ts +24 -0
- package/dist/hooks/useAuthForm.d.ts.map +1 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +2101 -2673
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/providers/AppProvider.d.ts +0 -3
- package/dist/providers/AppProvider.d.ts.map +1 -1
- package/dist/providers/AuthProvider.d.ts +33 -19
- package/dist/providers/AuthProvider.d.ts.map +1 -1
- package/dist/providers/RoutingProvider.d.ts.map +1 -1
- package/dist/providers/TenantProvider.d.ts +0 -2
- package/dist/providers/TenantProvider.d.ts.map +1 -1
- package/dist/services/AppApiService.d.ts +1 -3
- package/dist/services/AppApiService.d.ts.map +1 -1
- package/dist/services/AuthApiService.d.ts +4 -2
- package/dist/services/AuthApiService.d.ts.map +1 -1
- package/dist/services/FeatureFlagApiService.d.ts +1 -3
- package/dist/services/FeatureFlagApiService.d.ts.map +1 -1
- package/dist/services/PermissionApiService.d.ts +1 -3
- package/dist/services/PermissionApiService.d.ts.map +1 -1
- package/dist/services/RoleApiService.d.ts +1 -3
- package/dist/services/RoleApiService.d.ts.map +1 -1
- package/dist/services/SessionManager.d.ts +10 -14
- package/dist/services/SessionManager.d.ts.map +1 -1
- package/dist/services/SubscriptionApiService.d.ts +1 -3
- package/dist/services/SubscriptionApiService.d.ts.map +1 -1
- package/dist/services/SubscriptionPlanApiService.d.ts +1 -3
- package/dist/services/SubscriptionPlanApiService.d.ts.map +1 -1
- package/dist/services/TenantApiService.d.ts +1 -3
- package/dist/services/TenantApiService.d.ts.map +1 -1
- package/dist/services/UserApiService.d.ts +1 -3
- package/dist/services/UserApiService.d.ts.map +1 -1
- package/dist/utils/configValidation.d.ts +39 -0
- package/dist/utils/configValidation.d.ts.map +1 -0
- package/dist/utils/jwt.d.ts +25 -0
- package/dist/utils/jwt.d.ts.map +1 -0
- package/dist/utils/query.d.ts +6 -0
- package/dist/utils/query.d.ts.map +1 -0
- package/package.json +15 -16
- package/dist/utils/crossDomainAuth.d.ts +0 -37
- package/dist/utils/crossDomainAuth.d.ts.map +0 -1
- package/dist/utils/mappers.d.ts +0 -29
- package/dist/utils/mappers.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -8,11 +8,14 @@ A powerful, modern authentication and authorization library for React applicatio
|
|
|
8
8
|
- **✨ Magic Link Authentication** - Passwordless authentication via email with automatic verification
|
|
9
9
|
- **📧 Flexible Login** - Support for both email and phone number authentication
|
|
10
10
|
- **👥 Role-Based Access Control** - Granular permission system with role hierarchy
|
|
11
|
-
- **🛡️ Protected Components** -
|
|
12
|
-
- **📱 Multi-Tenant Support** -
|
|
11
|
+
- **🛡️ Protected Components & Zone Routing** - Declarative route access control (RFC-005)
|
|
12
|
+
- **📱 Multi-Tenant Support** - Subdomain, selector, and fixed tenant modes
|
|
13
13
|
- **🎯 TypeScript First** - Full TypeScript support with comprehensive type definitions
|
|
14
14
|
- **⚡ Modern React** - Built with React hooks and context for optimal performance
|
|
15
|
-
- **🔄 Session Management** - Automatic
|
|
15
|
+
- **🔄 Session Management** - Automatic refresh, proactive renewal, circuit breaker
|
|
16
|
+
- **🍪 Cross-subdomain Cookie Session** - Optional HttpOnly refresh cookie for seamless SSO between subdomains
|
|
17
|
+
- **🪟 Multi-tab Safety** - Web Locks API coordinates token refresh across browser tabs
|
|
18
|
+
- **🧩 Standalone AuthProvider** - Works with or without AppProvider/TenantProvider
|
|
16
19
|
- **🎨 Feature Flags** - Built-in feature flag management
|
|
17
20
|
- **💳 Subscription Management** - Integrated billing and subscription handling
|
|
18
21
|
- **🎛️ Fully Customizable Components** - All texts, styles, and icons are overridable via props
|
|
@@ -29,14 +32,14 @@ yarn add @skylabs-digital/react-identity-access
|
|
|
29
32
|
|
|
30
33
|
### 1. Setup Providers
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
You can use the library in **two modes**:
|
|
36
|
+
|
|
37
|
+
#### Option A — Multi-tenant setup (recommended for SaaS apps)
|
|
38
|
+
|
|
39
|
+
Wrap your application with the three providers in order:
|
|
33
40
|
|
|
34
41
|
```tsx
|
|
35
|
-
import {
|
|
36
|
-
AppProvider,
|
|
37
|
-
TenantProvider,
|
|
38
|
-
AuthProvider,
|
|
39
|
-
} from '@skylabs-digital/react-identity-access';
|
|
42
|
+
import { AppProvider, TenantProvider, AuthProvider } from '@skylabs-digital/react-identity-access';
|
|
40
43
|
|
|
41
44
|
function App() {
|
|
42
45
|
return (
|
|
@@ -48,19 +51,38 @@ function App() {
|
|
|
48
51
|
>
|
|
49
52
|
<TenantProvider
|
|
50
53
|
config={{
|
|
51
|
-
tenantMode: 'selector', // 'subdomain' | 'selector' | 'fixed'
|
|
54
|
+
tenantMode: 'selector', // 'subdomain' | 'selector' | 'fixed'
|
|
52
55
|
selectorParam: 'tenant',
|
|
53
56
|
}}
|
|
54
57
|
>
|
|
55
|
-
<AuthProvider>
|
|
56
|
-
{/* Your app components */}
|
|
57
|
-
</AuthProvider>
|
|
58
|
+
<AuthProvider>{/* Your app components */}</AuthProvider>
|
|
58
59
|
</TenantProvider>
|
|
59
60
|
</AppProvider>
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
#### Option B — Standalone AuthProvider (single-tenant or pure auth)
|
|
66
|
+
|
|
67
|
+
`AuthProvider` can run without `AppProvider` and `TenantProvider`. Pass `baseUrl` and `appId` directly via `AuthConfig` — ideal for simple apps that don't need multi-tenancy.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { AuthProvider } from '@skylabs-digital/react-identity-access';
|
|
71
|
+
|
|
72
|
+
function App() {
|
|
73
|
+
return (
|
|
74
|
+
<AuthProvider
|
|
75
|
+
config={{
|
|
76
|
+
baseUrl: 'https://your-api.com',
|
|
77
|
+
appId: 'your-app-id',
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{/* Your app components */}
|
|
81
|
+
</AuthProvider>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
64
86
|
### 2. Traditional Authentication
|
|
65
87
|
|
|
66
88
|
```tsx
|
|
@@ -151,64 +173,64 @@ Traditional email/phone + password login form.
|
|
|
151
173
|
|
|
152
174
|
#### Props (`LoginFormProps`)
|
|
153
175
|
|
|
154
|
-
| Prop
|
|
155
|
-
|
|
156
|
-
| `copy`
|
|
157
|
-
| `styles`
|
|
158
|
-
| `icons`
|
|
159
|
-
| `onSuccess`
|
|
160
|
-
| `onError`
|
|
161
|
-
| `onForgotPassword`
|
|
162
|
-
| `onSignupClick`
|
|
163
|
-
| `onMagicLinkClick`
|
|
164
|
-
| `showForgotPassword`
|
|
165
|
-
| `showSignupLink`
|
|
166
|
-
| `showMagicLinkOption` | `boolean`
|
|
167
|
-
| `className`
|
|
176
|
+
| Prop | Type | Default | Description |
|
|
177
|
+
| --------------------- | ------------------------- | --------------- | ------------------------------ |
|
|
178
|
+
| `copy` | `LoginFormCopy` | See below | Override user-facing texts |
|
|
179
|
+
| `styles` | `LoginFormStyles` | See below | Override inline styles |
|
|
180
|
+
| `icons` | `LoginFormIcons` | Eye/EyeOff SVGs | Override password toggle icons |
|
|
181
|
+
| `onSuccess` | `(data: any) => void` | — | Called after successful login |
|
|
182
|
+
| `onError` | `(error: string) => void` | — | Called on login failure |
|
|
183
|
+
| `onForgotPassword` | `() => void` | — | Navigate to forgot password |
|
|
184
|
+
| `onSignupClick` | `() => void` | — | Navigate to signup |
|
|
185
|
+
| `onMagicLinkClick` | `() => void` | — | Navigate to magic link |
|
|
186
|
+
| `showForgotPassword` | `boolean` | `true` | Show "Forgot password?" link |
|
|
187
|
+
| `showSignupLink` | `boolean` | `true` | Show signup link |
|
|
188
|
+
| `showMagicLinkOption` | `boolean` | `false` | Show magic link option |
|
|
189
|
+
| `className` | `string` | — | CSS class for the root element |
|
|
168
190
|
|
|
169
191
|
#### Copy (`LoginFormCopy`)
|
|
170
192
|
|
|
171
|
-
| Key
|
|
172
|
-
|
|
173
|
-
| `title`
|
|
174
|
-
| `usernameLabel`
|
|
175
|
-
| `usernamePlaceholder`
|
|
176
|
-
| `passwordLabel`
|
|
177
|
-
| `passwordPlaceholder`
|
|
178
|
-
| `submitButton`
|
|
179
|
-
| `loadingText`
|
|
180
|
-
| `errorMessage`
|
|
181
|
-
| `forgotPasswordLink`
|
|
182
|
-
| `signupLink`
|
|
183
|
-
| `signupText`
|
|
184
|
-
| `magicLinkText`
|
|
185
|
-
| `magicLinkLink`
|
|
186
|
-
| `tenantNotFoundError`
|
|
187
|
-
| `dividerBullet`
|
|
188
|
-
| `showPasswordAriaLabel` | `'Show password'`
|
|
189
|
-
| `hidePasswordAriaLabel` | `'Hide password'`
|
|
193
|
+
| Key | Default |
|
|
194
|
+
| ----------------------- | ------------------------------------ |
|
|
195
|
+
| `title` | `'Sign In'` |
|
|
196
|
+
| `usernameLabel` | `'Email or Phone'` |
|
|
197
|
+
| `usernamePlaceholder` | `'Enter your email or phone number'` |
|
|
198
|
+
| `passwordLabel` | `'Password'` |
|
|
199
|
+
| `passwordPlaceholder` | `'Enter your password'` |
|
|
200
|
+
| `submitButton` | `'Sign In'` |
|
|
201
|
+
| `loadingText` | `'Signing in...'` |
|
|
202
|
+
| `errorMessage` | `'Invalid credentials'` |
|
|
203
|
+
| `forgotPasswordLink` | `'Forgot your password?'` |
|
|
204
|
+
| `signupLink` | `'Sign up here'` |
|
|
205
|
+
| `signupText` | `"Don't have an account?"` |
|
|
206
|
+
| `magicLinkText` | `'Prefer passwordless?'` |
|
|
207
|
+
| `magicLinkLink` | `'Use Magic Link'` |
|
|
208
|
+
| `tenantNotFoundError` | `'Tenant not found'` |
|
|
209
|
+
| `dividerBullet` | `'•'` |
|
|
210
|
+
| `showPasswordAriaLabel` | `'Show password'` |
|
|
211
|
+
| `hidePasswordAriaLabel` | `'Hide password'` |
|
|
190
212
|
|
|
191
213
|
#### Styles (`LoginFormStyles`)
|
|
192
214
|
|
|
193
|
-
| Key
|
|
194
|
-
|
|
195
|
-
| `container`
|
|
196
|
-
| `title`
|
|
197
|
-
| `form`
|
|
198
|
-
| `fieldGroup`
|
|
199
|
-
| `label`
|
|
200
|
-
| `input`
|
|
201
|
-
| `inputError`
|
|
202
|
-
| `inputContainer` | Password field wrapper
|
|
203
|
-
| `inputWithIcon`
|
|
204
|
-
| `passwordToggle` | Show/hide password button
|
|
205
|
-
| `button`
|
|
206
|
-
| `buttonDisabled` | Disabled state (merged on top of `button`)
|
|
207
|
-
| `buttonLoading`
|
|
208
|
-
| `errorText`
|
|
209
|
-
| `linkContainer`
|
|
210
|
-
| `link`
|
|
211
|
-
| `divider`
|
|
215
|
+
| Key | Targets |
|
|
216
|
+
| ---------------- | ----------------------------------------------- |
|
|
217
|
+
| `container` | Root wrapper |
|
|
218
|
+
| `title` | `<h2>` heading |
|
|
219
|
+
| `form` | `<form>` element |
|
|
220
|
+
| `fieldGroup` | Each label+input group |
|
|
221
|
+
| `label` | `<label>` elements |
|
|
222
|
+
| `input` | `<input>` elements |
|
|
223
|
+
| `inputError` | Input in error state (merged on top of `input`) |
|
|
224
|
+
| `inputContainer` | Password field wrapper |
|
|
225
|
+
| `inputWithIcon` | Password input with toggle icon |
|
|
226
|
+
| `passwordToggle` | Show/hide password button |
|
|
227
|
+
| `button` | Submit button |
|
|
228
|
+
| `buttonDisabled` | Disabled state (merged on top of `button`) |
|
|
229
|
+
| `buttonLoading` | Loading state (merged on top of `button`) |
|
|
230
|
+
| `errorText` | Error message text |
|
|
231
|
+
| `linkContainer` | Links section wrapper |
|
|
232
|
+
| `link` | `<a>` link elements |
|
|
233
|
+
| `divider` | Bullet divider between links |
|
|
212
234
|
|
|
213
235
|
#### Usage Example
|
|
214
236
|
|
|
@@ -228,7 +250,7 @@ Traditional email/phone + password login form.
|
|
|
228
250
|
showPassword: <MyEyeIcon />,
|
|
229
251
|
hidePassword: <MyEyeOffIcon />,
|
|
230
252
|
}}
|
|
231
|
-
onSuccess={
|
|
253
|
+
onSuccess={data => navigate('/dashboard')}
|
|
232
254
|
onMagicLinkClick={() => navigate('/magic-link')}
|
|
233
255
|
showMagicLinkOption
|
|
234
256
|
/>
|
|
@@ -242,74 +264,74 @@ User registration form with email/phone, password, and optional tenant creation.
|
|
|
242
264
|
|
|
243
265
|
#### Props (`SignupFormProps`)
|
|
244
266
|
|
|
245
|
-
| Prop
|
|
246
|
-
|
|
247
|
-
| `copy`
|
|
248
|
-
| `styles`
|
|
249
|
-
| `signupType`
|
|
250
|
-
| `onSuccess`
|
|
251
|
-
| `onError`
|
|
252
|
-
| `onLoginClick`
|
|
253
|
-
| `onMagicLinkClick`
|
|
254
|
-
| `showLoginLink`
|
|
255
|
-
| `showMagicLinkOption` | `boolean`
|
|
256
|
-
| `className`
|
|
267
|
+
| Prop | Type | Default | Description |
|
|
268
|
+
| --------------------- | ------------------------- | --------- | ---------------------------------- |
|
|
269
|
+
| `copy` | `SignupFormCopy` | See below | Override user-facing texts |
|
|
270
|
+
| `styles` | `SignupFormStyles` | See below | Override inline styles |
|
|
271
|
+
| `signupType` | `'user' \| 'tenant'` | `'user'` | User signup or tenant admin signup |
|
|
272
|
+
| `onSuccess` | `(data: any) => void` | — | Called after successful signup |
|
|
273
|
+
| `onError` | `(error: string) => void` | — | Called on signup failure |
|
|
274
|
+
| `onLoginClick` | `() => void` | — | Navigate to login |
|
|
275
|
+
| `onMagicLinkClick` | `() => void` | — | Navigate to magic link |
|
|
276
|
+
| `showLoginLink` | `boolean` | `true` | Show login link |
|
|
277
|
+
| `showMagicLinkOption` | `boolean` | `false` | Show magic link option |
|
|
278
|
+
| `className` | `string` | — | CSS class for the root element |
|
|
257
279
|
|
|
258
280
|
#### Copy (`SignupFormCopy`)
|
|
259
281
|
|
|
260
|
-
| Key
|
|
261
|
-
|
|
262
|
-
| `title`
|
|
263
|
-
| `nameLabel`
|
|
264
|
-
| `namePlaceholder`
|
|
265
|
-
| `lastNameLabel`
|
|
266
|
-
| `lastNamePlaceholder`
|
|
267
|
-
| `emailLabel`
|
|
268
|
-
| `emailPlaceholder`
|
|
269
|
-
| `phoneNumberLabel`
|
|
270
|
-
| `phoneNumberPlaceholder`
|
|
271
|
-
| `passwordLabel`
|
|
272
|
-
| `passwordPlaceholder`
|
|
273
|
-
| `confirmPasswordLabel`
|
|
274
|
-
| `confirmPasswordPlaceholder` | `'Confirm your password'`
|
|
275
|
-
| `tenantNameLabel`
|
|
276
|
-
| `tenantNamePlaceholder`
|
|
277
|
-
| `submitButton`
|
|
278
|
-
| `loadingText`
|
|
279
|
-
| `errorMessage`
|
|
280
|
-
| `passwordMismatchError`
|
|
281
|
-
| `loginLink`
|
|
282
|
-
| `loginText`
|
|
283
|
-
| `magicLinkText`
|
|
284
|
-
| `magicLinkLink`
|
|
285
|
-
| `isAdminLabel`
|
|
286
|
-
| `isAdminDescription`
|
|
287
|
-
| `contactMethodHint`
|
|
288
|
-
| `tenantNotFoundError`
|
|
289
|
-
| `dividerBullet`
|
|
282
|
+
| Key | Default |
|
|
283
|
+
| ---------------------------- | ------------------------------------------------------------ |
|
|
284
|
+
| `title` | `'Create Account'` |
|
|
285
|
+
| `nameLabel` | `'First Name'` |
|
|
286
|
+
| `namePlaceholder` | `'Enter your first name'` |
|
|
287
|
+
| `lastNameLabel` | `'Last Name'` |
|
|
288
|
+
| `lastNamePlaceholder` | `'Enter your last name'` |
|
|
289
|
+
| `emailLabel` | `'Email'` |
|
|
290
|
+
| `emailPlaceholder` | `'Enter your email'` |
|
|
291
|
+
| `phoneNumberLabel` | `'Phone Number'` |
|
|
292
|
+
| `phoneNumberPlaceholder` | `'Enter your phone number'` |
|
|
293
|
+
| `passwordLabel` | `'Password'` |
|
|
294
|
+
| `passwordPlaceholder` | `'Enter your password'` |
|
|
295
|
+
| `confirmPasswordLabel` | `'Confirm Password'` |
|
|
296
|
+
| `confirmPasswordPlaceholder` | `'Confirm your password'` |
|
|
297
|
+
| `tenantNameLabel` | `'Organization Name'` |
|
|
298
|
+
| `tenantNamePlaceholder` | `'Enter your organization name'` |
|
|
299
|
+
| `submitButton` | `'Create Account'` |
|
|
300
|
+
| `loadingText` | `'Creating account...'` |
|
|
301
|
+
| `errorMessage` | `'Failed to create account'` |
|
|
302
|
+
| `passwordMismatchError` | `'Passwords do not match'` |
|
|
303
|
+
| `loginLink` | `'Sign in here'` |
|
|
304
|
+
| `loginText` | `'Already have an account?'` |
|
|
305
|
+
| `magicLinkText` | `'Prefer passwordless?'` |
|
|
306
|
+
| `magicLinkLink` | `'Use Magic Link'` |
|
|
307
|
+
| `isAdminLabel` | `'Create new organization'` |
|
|
308
|
+
| `isAdminDescription` | `'Check this if you want to create a new organization'` |
|
|
309
|
+
| `contactMethodHint` | `'At least one contact method (email or phone) is required'` |
|
|
310
|
+
| `tenantNotFoundError` | `'Tenant not found'` |
|
|
311
|
+
| `dividerBullet` | `'•'` |
|
|
290
312
|
|
|
291
313
|
#### Styles (`SignupFormStyles`)
|
|
292
314
|
|
|
293
|
-
| Key
|
|
294
|
-
|
|
295
|
-
| `container`
|
|
296
|
-
| `title`
|
|
297
|
-
| `form`
|
|
298
|
-
| `fieldGroup`
|
|
299
|
-
| `label`
|
|
300
|
-
| `input`
|
|
301
|
-
| `inputError`
|
|
302
|
-
| `checkbox`
|
|
303
|
-
| `checkboxContainer` | Checkbox + label wrapper
|
|
304
|
-
| `checkboxLabel`
|
|
305
|
-
| `button`
|
|
306
|
-
| `buttonDisabled`
|
|
307
|
-
| `buttonLoading`
|
|
308
|
-
| `errorText`
|
|
309
|
-
| `linkContainer`
|
|
310
|
-
| `link`
|
|
311
|
-
| `divider`
|
|
312
|
-
| `hintText`
|
|
315
|
+
| Key | Targets |
|
|
316
|
+
| ------------------- | ------------------------------------------ |
|
|
317
|
+
| `container` | Root wrapper |
|
|
318
|
+
| `title` | `<h2>` heading |
|
|
319
|
+
| `form` | `<form>` element |
|
|
320
|
+
| `fieldGroup` | Each label+input group |
|
|
321
|
+
| `label` | `<label>` elements |
|
|
322
|
+
| `input` | `<input>` elements |
|
|
323
|
+
| `inputError` | Input in error state |
|
|
324
|
+
| `checkbox` | Checkbox input |
|
|
325
|
+
| `checkboxContainer` | Checkbox + label wrapper |
|
|
326
|
+
| `checkboxLabel` | Checkbox label text |
|
|
327
|
+
| `button` | Submit button |
|
|
328
|
+
| `buttonDisabled` | Disabled state (merged on top of `button`) |
|
|
329
|
+
| `buttonLoading` | Loading state (merged on top of `button`) |
|
|
330
|
+
| `errorText` | Error message text |
|
|
331
|
+
| `linkContainer` | Links section wrapper |
|
|
332
|
+
| `link` | `<a>` link elements |
|
|
333
|
+
| `divider` | Bullet divider between links |
|
|
334
|
+
| `hintText` | Contact method hint text |
|
|
313
335
|
|
|
314
336
|
---
|
|
315
337
|
|
|
@@ -319,71 +341,71 @@ Passwordless Magic Link send form. Handles both new and existing users.
|
|
|
319
341
|
|
|
320
342
|
#### Props (`MagicLinkFormProps`)
|
|
321
343
|
|
|
322
|
-
| Prop
|
|
323
|
-
|
|
324
|
-
| `copy`
|
|
325
|
-
| `styles`
|
|
326
|
-
| `onSuccess`
|
|
327
|
-
| `onError`
|
|
328
|
-
| `onLoginClick`
|
|
329
|
-
| `onSignupClick`
|
|
330
|
-
| `showTraditionalLinks` | `boolean`
|
|
331
|
-
| `className`
|
|
332
|
-
| `verifyToken`
|
|
333
|
-
| `frontendUrl`
|
|
344
|
+
| Prop | Type | Default | Description |
|
|
345
|
+
| ---------------------- | ------------------------- | ------------------------ | ------------------------------------ |
|
|
346
|
+
| `copy` | `MagicLinkFormCopy` | See below | Override user-facing texts |
|
|
347
|
+
| `styles` | `MagicLinkFormStyles` | See below | Override inline styles |
|
|
348
|
+
| `onSuccess` | `(data: any) => void` | — | Called after magic link sent |
|
|
349
|
+
| `onError` | `(error: string) => void` | — | Called on failure |
|
|
350
|
+
| `onLoginClick` | `() => void` | — | Navigate to login |
|
|
351
|
+
| `onSignupClick` | `() => void` | — | Navigate to signup |
|
|
352
|
+
| `showTraditionalLinks` | `boolean` | `true` | Show login/signup links |
|
|
353
|
+
| `className` | `string` | — | CSS class for the root element |
|
|
354
|
+
| `verifyToken` | `string` | — | Auto-verify a magic link token |
|
|
355
|
+
| `frontendUrl` | `string` | `window.location.origin` | Base URL for the magic link callback |
|
|
334
356
|
|
|
335
357
|
#### Copy (`MagicLinkFormCopy`)
|
|
336
358
|
|
|
337
|
-
| Key
|
|
338
|
-
|
|
339
|
-
| `title`
|
|
340
|
-
| `description`
|
|
341
|
-
| `emailLabel`
|
|
342
|
-
| `emailPlaceholder`
|
|
343
|
-
| `nameLabel`
|
|
344
|
-
| `namePlaceholder`
|
|
345
|
-
| `lastNameLabel`
|
|
346
|
-
| `lastNamePlaceholder`
|
|
347
|
-
| `submitButton`
|
|
348
|
-
| `loadingText`
|
|
349
|
-
| `successMessage`
|
|
350
|
-
| `errorMessage`
|
|
351
|
-
| `verifyingText`
|
|
352
|
-
| `verifyingDescription`
|
|
353
|
-
| `showNameToggle`
|
|
354
|
-
| `hideNameToggle`
|
|
355
|
-
| `loginLink`
|
|
356
|
-
| `loginText`
|
|
357
|
-
| `signupLink`
|
|
358
|
-
| `signupText`
|
|
359
|
-
| `tenantNotFoundError`
|
|
360
|
-
| `missingTenantOrEmailError` | `'Missing tenant or email'`
|
|
361
|
-
| `dividerBullet`
|
|
359
|
+
| Key | Default |
|
|
360
|
+
| --------------------------- | -------------------------------------------------- |
|
|
361
|
+
| `title` | `'Sign In with Magic Link'` |
|
|
362
|
+
| `description` | `"Enter your email to receive a magic link..."` |
|
|
363
|
+
| `emailLabel` | `'Email'` |
|
|
364
|
+
| `emailPlaceholder` | `'Enter your email'` |
|
|
365
|
+
| `nameLabel` | `'Name'` |
|
|
366
|
+
| `namePlaceholder` | `'Enter your name'` |
|
|
367
|
+
| `lastNameLabel` | `'Last Name'` |
|
|
368
|
+
| `lastNamePlaceholder` | `'Enter your last name'` |
|
|
369
|
+
| `submitButton` | `'Send Magic Link'` |
|
|
370
|
+
| `loadingText` | `'Sending magic link...'` |
|
|
371
|
+
| `successMessage` | `'Magic link sent! Check your email...'` |
|
|
372
|
+
| `errorMessage` | `'Failed to send magic link. Please try again.'` |
|
|
373
|
+
| `verifyingText` | `'Verifying magic link...'` |
|
|
374
|
+
| `verifyingDescription` | `'Please wait while we verify your magic link...'` |
|
|
375
|
+
| `showNameToggle` | `'New user? Add your name'` |
|
|
376
|
+
| `hideNameToggle` | `'Existing user? Hide name fields'` |
|
|
377
|
+
| `loginLink` | `'Sign in with password'` |
|
|
378
|
+
| `loginText` | `'Already have an account?'` |
|
|
379
|
+
| `signupLink` | `'Sign up with password'` |
|
|
380
|
+
| `signupText` | `'Prefer traditional signup?'` |
|
|
381
|
+
| `tenantNotFoundError` | `'Tenant not found'` |
|
|
382
|
+
| `missingTenantOrEmailError` | `'Missing tenant or email'` |
|
|
383
|
+
| `dividerBullet` | `'•'` |
|
|
362
384
|
|
|
363
385
|
#### Styles (`MagicLinkFormStyles`)
|
|
364
386
|
|
|
365
|
-
| Key
|
|
366
|
-
|
|
367
|
-
| `container`
|
|
368
|
-
| `title`
|
|
369
|
-
| `description`
|
|
370
|
-
| `form`
|
|
371
|
-
| `fieldGroup`
|
|
372
|
-
| `label`
|
|
373
|
-
| `input`
|
|
374
|
-
| `inputError`
|
|
375
|
-
| `button`
|
|
376
|
-
| `buttonDisabled`
|
|
377
|
-
| `buttonLoading`
|
|
378
|
-
| `errorText`
|
|
379
|
-
| `successText`
|
|
380
|
-
| `linkContainer`
|
|
381
|
-
| `link`
|
|
382
|
-
| `divider`
|
|
383
|
-
| `verifyingContainer` | Verification loading wrapper
|
|
384
|
-
| `verifyingText`
|
|
385
|
-
| `toggleContainer`
|
|
386
|
-
| `toggleLink`
|
|
387
|
+
| Key | Targets |
|
|
388
|
+
| -------------------- | ------------------------------------------ |
|
|
389
|
+
| `container` | Root wrapper |
|
|
390
|
+
| `title` | `<h2>` heading |
|
|
391
|
+
| `description` | Description paragraph |
|
|
392
|
+
| `form` | `<form>` element |
|
|
393
|
+
| `fieldGroup` | Each label+input group |
|
|
394
|
+
| `label` | `<label>` elements |
|
|
395
|
+
| `input` | `<input>` elements |
|
|
396
|
+
| `inputError` | Input in error state |
|
|
397
|
+
| `button` | Submit button |
|
|
398
|
+
| `buttonDisabled` | Disabled state (merged on top of `button`) |
|
|
399
|
+
| `buttonLoading` | Loading state (merged on top of `button`) |
|
|
400
|
+
| `errorText` | Error message text |
|
|
401
|
+
| `successText` | Success message text |
|
|
402
|
+
| `linkContainer` | Links section wrapper |
|
|
403
|
+
| `link` | `<a>` link elements |
|
|
404
|
+
| `divider` | Bullet divider between links |
|
|
405
|
+
| `verifyingContainer` | Verification loading wrapper |
|
|
406
|
+
| `verifyingText` | Verification description text |
|
|
407
|
+
| `toggleContainer` | Name fields toggle wrapper |
|
|
408
|
+
| `toggleLink` | "New user? Add your name" toggle button |
|
|
387
409
|
|
|
388
410
|
---
|
|
389
411
|
|
|
@@ -393,59 +415,59 @@ Automatic Magic Link verification component. Reads token from URL params or acce
|
|
|
393
415
|
|
|
394
416
|
#### Props (`MagicLinkVerifyProps`)
|
|
395
417
|
|
|
396
|
-
| Prop
|
|
397
|
-
|
|
398
|
-
| `copy`
|
|
399
|
-
| `styles`
|
|
400
|
-
| `icons`
|
|
401
|
-
| `onSuccess`
|
|
402
|
-
| `onError`
|
|
403
|
-
| `onRetry`
|
|
404
|
-
| `onBackToLogin`
|
|
405
|
-
| `className`
|
|
406
|
-
| `token`
|
|
407
|
-
| `email`
|
|
408
|
-
| `appId`
|
|
409
|
-
| `tenantSlug`
|
|
410
|
-
| `autoRedirectDelay` | `number`
|
|
418
|
+
| Prop | Type | Default | Description |
|
|
419
|
+
| ------------------- | ------------------------- | --------- | ------------------------------------------------ |
|
|
420
|
+
| `copy` | `MagicLinkVerifyCopy` | See below | Override user-facing texts |
|
|
421
|
+
| `styles` | `MagicLinkVerifyStyles` | See below | Override inline styles |
|
|
422
|
+
| `icons` | `MagicLinkVerifyIcons` | SVG icons | Override loading/success/error icons |
|
|
423
|
+
| `onSuccess` | `(data: any) => void` | — | Called after successful verification |
|
|
424
|
+
| `onError` | `(error: string) => void` | — | Called on verification failure |
|
|
425
|
+
| `onRetry` | `() => void` | — | Called before retry attempt |
|
|
426
|
+
| `onBackToLogin` | `() => void` | — | Navigate back to login |
|
|
427
|
+
| `className` | `string` | — | CSS class for the root element |
|
|
428
|
+
| `token` | `string` | URL param | Magic link token (auto-extracted from `?token=`) |
|
|
429
|
+
| `email` | `string` | URL param | User email (auto-extracted from `?email=`) |
|
|
430
|
+
| `appId` | `string` | URL param | App ID (auto-extracted from `?appId=`) |
|
|
431
|
+
| `tenantSlug` | `string` | URL param | Tenant slug (auto-extracted from `?tenantSlug=`) |
|
|
432
|
+
| `autoRedirectDelay` | `number` | `3000` | Milliseconds before auto-redirect (0 to disable) |
|
|
411
433
|
|
|
412
434
|
#### Copy (`MagicLinkVerifyCopy`)
|
|
413
435
|
|
|
414
|
-
| Key
|
|
415
|
-
|
|
416
|
-
| `title`
|
|
417
|
-
| `verifyingMessage`
|
|
418
|
-
| `successMessage`
|
|
419
|
-
| `errorMessage`
|
|
420
|
-
| `redirectingMessage` | `'Redirecting you to the dashboard...'`
|
|
421
|
-
| `retryButton`
|
|
422
|
-
| `backToLoginButton`
|
|
423
|
-
| `missingParamsError` | `'Missing required parameters: token or email'`
|
|
436
|
+
| Key | Default |
|
|
437
|
+
| -------------------- | -------------------------------------------------------------------- |
|
|
438
|
+
| `title` | `'Verifying Magic Link'` |
|
|
439
|
+
| `verifyingMessage` | `'Please wait while we verify your magic link...'` |
|
|
440
|
+
| `successMessage` | `'Magic link verified successfully! You are now logged in.'` |
|
|
441
|
+
| `errorMessage` | `'Failed to verify magic link. The link may be expired or invalid.'` |
|
|
442
|
+
| `redirectingMessage` | `'Redirecting you to the dashboard...'` |
|
|
443
|
+
| `retryButton` | `'Try Again'` |
|
|
444
|
+
| `backToLoginButton` | `'Back to Login'` |
|
|
445
|
+
| `missingParamsError` | `'Missing required parameters: token or email'` |
|
|
424
446
|
|
|
425
447
|
#### Styles (`MagicLinkVerifyStyles`)
|
|
426
448
|
|
|
427
|
-
| Key
|
|
428
|
-
|
|
429
|
-
| `container`
|
|
430
|
-
| `card`
|
|
431
|
-
| `title`
|
|
432
|
-
| `message`
|
|
433
|
-
| `successMessage`
|
|
434
|
-
| `errorMessage`
|
|
435
|
-
| `spinner`
|
|
436
|
-
| `buttonContainer`
|
|
437
|
-
| `retryButton`
|
|
438
|
-
| `retryButtonHover` | Hover state for retry button
|
|
439
|
-
| `backButton`
|
|
440
|
-
| `backButtonHover`
|
|
449
|
+
| Key | Targets |
|
|
450
|
+
| ------------------ | ----------------------------------- |
|
|
451
|
+
| `container` | Root wrapper |
|
|
452
|
+
| `card` | Inner card (kept for compatibility) |
|
|
453
|
+
| `title` | `<h1>` heading |
|
|
454
|
+
| `message` | Verifying/redirecting message |
|
|
455
|
+
| `successMessage` | Success state message |
|
|
456
|
+
| `errorMessage` | Error state message |
|
|
457
|
+
| `spinner` | Loading spinner |
|
|
458
|
+
| `buttonContainer` | Error buttons wrapper |
|
|
459
|
+
| `retryButton` | "Try Again" button |
|
|
460
|
+
| `retryButtonHover` | Hover state for retry button |
|
|
461
|
+
| `backButton` | "Back to Login" button |
|
|
462
|
+
| `backButtonHover` | Hover state for back button |
|
|
441
463
|
|
|
442
464
|
#### Icons (`MagicLinkVerifyIcons`)
|
|
443
465
|
|
|
444
|
-
| Key
|
|
445
|
-
|
|
446
|
-
| `loading` | Animated spinner
|
|
447
|
-
| `success` | Green checkmark SVG | Shown on success
|
|
448
|
-
| `error`
|
|
466
|
+
| Key | Default | Description |
|
|
467
|
+
| --------- | ------------------- | ------------------------- |
|
|
468
|
+
| `loading` | Animated spinner | Shown during verification |
|
|
469
|
+
| `success` | Green checkmark SVG | Shown on success |
|
|
470
|
+
| `error` | Red X circle SVG | Shown on error |
|
|
449
471
|
|
|
450
472
|
---
|
|
451
473
|
|
|
@@ -455,68 +477,68 @@ Password reset flow with two modes: request (send email) and reset (set new pass
|
|
|
455
477
|
|
|
456
478
|
#### Props (`PasswordRecoveryFormProps`)
|
|
457
479
|
|
|
458
|
-
| Prop
|
|
459
|
-
|
|
460
|
-
| `copy`
|
|
461
|
-
| `styles`
|
|
462
|
-
| `mode`
|
|
463
|
-
| `token`
|
|
464
|
-
| `onSuccess`
|
|
465
|
-
| `onError`
|
|
466
|
-
| `onBackToLogin` | `() => void`
|
|
467
|
-
| `onModeChange`
|
|
468
|
-
| `className`
|
|
480
|
+
| Prop | Type | Default | Description |
|
|
481
|
+
| --------------- | -------------------------------------- | ----------- | ------------------------------ |
|
|
482
|
+
| `copy` | `PasswordRecoveryFormCopy` | See below | Override user-facing texts |
|
|
483
|
+
| `styles` | `PasswordRecoveryFormStyles` | See below | Override inline styles |
|
|
484
|
+
| `mode` | `'request' \| 'reset'` | `'request'` | Current form mode |
|
|
485
|
+
| `token` | `string` | — | Pre-fill reset token |
|
|
486
|
+
| `onSuccess` | `(data?: any) => void` | — | Called after success |
|
|
487
|
+
| `onError` | `(error: string) => void` | — | Called on failure |
|
|
488
|
+
| `onBackToLogin` | `() => void` | — | Navigate back to login |
|
|
489
|
+
| `onModeChange` | `(mode: 'request' \| 'reset') => void` | — | Show mode switch links |
|
|
490
|
+
| `className` | `string` | — | CSS class for the root element |
|
|
469
491
|
|
|
470
492
|
#### Copy (`PasswordRecoveryFormCopy`)
|
|
471
493
|
|
|
472
|
-
| Key
|
|
473
|
-
|
|
474
|
-
| `title`
|
|
475
|
-
| `subtitle`
|
|
476
|
-
| `emailLabel`
|
|
477
|
-
| `emailPlaceholder`
|
|
478
|
-
| `submitButton`
|
|
479
|
-
| `loadingText`
|
|
480
|
-
| `successMessage`
|
|
481
|
-
| `errorMessage`
|
|
482
|
-
| `backToLoginLink`
|
|
483
|
-
| `resetTitle`
|
|
484
|
-
| `resetSubtitle`
|
|
485
|
-
| `tokenLabel`
|
|
486
|
-
| `tokenPlaceholder`
|
|
487
|
-
| `newPasswordLabel`
|
|
488
|
-
| `newPasswordPlaceholder`
|
|
489
|
-
| `confirmPasswordLabel`
|
|
490
|
-
| `confirmPasswordPlaceholder` | `'Confirm new password'`
|
|
491
|
-
| `resetSubmitButton`
|
|
492
|
-
| `resetLoadingText`
|
|
493
|
-
| `resetSuccessMessage`
|
|
494
|
-
| `passwordMismatchError`
|
|
495
|
-
| `requestNewLinkLink`
|
|
496
|
-
| `haveTokenLink`
|
|
497
|
-
| `tenantNotFoundError`
|
|
498
|
-
| `dividerBullet`
|
|
494
|
+
| Key | Default |
|
|
495
|
+
| ---------------------------- | --------------------------------------------------------- |
|
|
496
|
+
| `title` | `'Reset Password'` |
|
|
497
|
+
| `subtitle` | `"Enter your email address and we'll send you a link..."` |
|
|
498
|
+
| `emailLabel` | `'Email'` |
|
|
499
|
+
| `emailPlaceholder` | `'Enter your email'` |
|
|
500
|
+
| `submitButton` | `'Send Reset Link'` |
|
|
501
|
+
| `loadingText` | `'Sending...'` |
|
|
502
|
+
| `successMessage` | `'Password reset link sent! Check your email.'` |
|
|
503
|
+
| `errorMessage` | `'Failed to send reset link'` |
|
|
504
|
+
| `backToLoginLink` | `'Back to Sign In'` |
|
|
505
|
+
| `resetTitle` | `'Set New Password'` |
|
|
506
|
+
| `resetSubtitle` | `'Enter your reset token and new password.'` |
|
|
507
|
+
| `tokenLabel` | `'Reset Token'` |
|
|
508
|
+
| `tokenPlaceholder` | `'Enter reset token from email'` |
|
|
509
|
+
| `newPasswordLabel` | `'New Password'` |
|
|
510
|
+
| `newPasswordPlaceholder` | `'Enter new password'` |
|
|
511
|
+
| `confirmPasswordLabel` | `'Confirm Password'` |
|
|
512
|
+
| `confirmPasswordPlaceholder` | `'Confirm new password'` |
|
|
513
|
+
| `resetSubmitButton` | `'Reset Password'` |
|
|
514
|
+
| `resetLoadingText` | `'Resetting...'` |
|
|
515
|
+
| `resetSuccessMessage` | `'Password reset successfully!'` |
|
|
516
|
+
| `passwordMismatchError` | `'Passwords do not match'` |
|
|
517
|
+
| `requestNewLinkLink` | `'Request New Link'` |
|
|
518
|
+
| `haveTokenLink` | `'I have a token'` |
|
|
519
|
+
| `tenantNotFoundError` | `'Tenant not found'` |
|
|
520
|
+
| `dividerBullet` | `'•'` |
|
|
499
521
|
|
|
500
522
|
#### Styles (`PasswordRecoveryFormStyles`)
|
|
501
523
|
|
|
502
|
-
| Key
|
|
503
|
-
|
|
504
|
-
| `container`
|
|
505
|
-
| `title`
|
|
506
|
-
| `subtitle`
|
|
507
|
-
| `form`
|
|
508
|
-
| `fieldGroup`
|
|
509
|
-
| `label`
|
|
510
|
-
| `input`
|
|
511
|
-
| `inputError`
|
|
512
|
-
| `button`
|
|
513
|
-
| `buttonDisabled`
|
|
514
|
-
| `buttonLoading`
|
|
515
|
-
| `errorText`
|
|
516
|
-
| `successText`
|
|
517
|
-
| `linkContainer`
|
|
518
|
-
| `link`
|
|
519
|
-
| `modeSwitchDivider` | Bullet divider between mode links
|
|
524
|
+
| Key | Targets |
|
|
525
|
+
| ------------------- | ------------------------------------------ |
|
|
526
|
+
| `container` | Root wrapper |
|
|
527
|
+
| `title` | `<h2>` heading |
|
|
528
|
+
| `subtitle` | Subtitle paragraph |
|
|
529
|
+
| `form` | `<form>` element |
|
|
530
|
+
| `fieldGroup` | Each label+input group |
|
|
531
|
+
| `label` | `<label>` elements |
|
|
532
|
+
| `input` | `<input>` elements |
|
|
533
|
+
| `inputError` | Input in error state |
|
|
534
|
+
| `button` | Submit button |
|
|
535
|
+
| `buttonDisabled` | Disabled state (merged on top of `button`) |
|
|
536
|
+
| `buttonLoading` | Loading state (merged on top of `button`) |
|
|
537
|
+
| `errorText` | Error message text |
|
|
538
|
+
| `successText` | Success message text |
|
|
539
|
+
| `linkContainer` | Links section wrapper |
|
|
540
|
+
| `link` | `<a>` link elements |
|
|
541
|
+
| `modeSwitchDivider` | Bullet divider between mode links |
|
|
520
542
|
|
|
521
543
|
---
|
|
522
544
|
|
|
@@ -526,33 +548,33 @@ Dropdown component for switching between tenants. Integrates with `AuthProvider`
|
|
|
526
548
|
|
|
527
549
|
#### Props (`TenantSelectorProps`)
|
|
528
550
|
|
|
529
|
-
| Prop
|
|
530
|
-
|
|
531
|
-
| `tenants`
|
|
532
|
-
| `currentTenantId`
|
|
533
|
-
| `onSelect`
|
|
534
|
-
| `styles`
|
|
535
|
-
| `className`
|
|
536
|
-
| `dropdownClassName` | `string`
|
|
537
|
-
| `itemClassName`
|
|
538
|
-
| `renderItem`
|
|
539
|
-
| `placeholder`
|
|
540
|
-
| `disabled`
|
|
541
|
-
| `showCurrentTenant` | `boolean`
|
|
551
|
+
| Prop | Type | Default | Description |
|
|
552
|
+
| ------------------- | ----------------------------------- | --------------------- | ----------------------------------- |
|
|
553
|
+
| `tenants` | `UserTenantMembership[]` | From context | Override tenant list |
|
|
554
|
+
| `currentTenantId` | `string \| null` | From context | Override current tenant |
|
|
555
|
+
| `onSelect` | `(tenantId: string) => void` | `auth.switchToTenant` | Custom selection handler |
|
|
556
|
+
| `styles` | `TenantSelectorStyles` | See below | Override inline styles |
|
|
557
|
+
| `className` | `string` | — | CSS class for root element |
|
|
558
|
+
| `dropdownClassName` | `string` | — | CSS class for dropdown |
|
|
559
|
+
| `itemClassName` | `string` | — | CSS class for each item |
|
|
560
|
+
| `renderItem` | `(tenant, isSelected) => ReactNode` | Default renderer | Custom item renderer |
|
|
561
|
+
| `placeholder` | `string` | `'Select tenant'` | Placeholder when no tenant selected |
|
|
562
|
+
| `disabled` | `boolean` | `false` | Disable the selector |
|
|
563
|
+
| `showCurrentTenant` | `boolean` | `true` | Show name when only 1 tenant |
|
|
542
564
|
|
|
543
565
|
#### Styles (`TenantSelectorStyles`)
|
|
544
566
|
|
|
545
|
-
| Key
|
|
546
|
-
|
|
547
|
-
| `wrapper`
|
|
548
|
-
| `button`
|
|
567
|
+
| Key | Targets |
|
|
568
|
+
| ---------------- | ------------------------------------------------- |
|
|
569
|
+
| `wrapper` | Root `<div>` (position: relative) |
|
|
570
|
+
| `button` | Trigger button |
|
|
549
571
|
| `buttonDisabled` | Disabled button state (merged on top of `button`) |
|
|
550
|
-
| `dropdown`
|
|
551
|
-
| `item`
|
|
552
|
-
| `itemSelected`
|
|
553
|
-
| `itemHover`
|
|
554
|
-
| `itemRole`
|
|
555
|
-
| `arrow`
|
|
572
|
+
| `dropdown` | Dropdown menu container |
|
|
573
|
+
| `item` | Each tenant item |
|
|
574
|
+
| `itemSelected` | Selected tenant item (merged on top of `item`) |
|
|
575
|
+
| `itemHover` | Hover state for items |
|
|
576
|
+
| `itemRole` | Role badge next to tenant name |
|
|
577
|
+
| `arrow` | Arrow indicator (▲/▼) |
|
|
556
578
|
|
|
557
579
|
---
|
|
558
580
|
|
|
@@ -570,13 +592,13 @@ Conditionally renders content based on permissions and/or roles.
|
|
|
570
592
|
</Protected>
|
|
571
593
|
```
|
|
572
594
|
|
|
573
|
-
| Prop
|
|
574
|
-
|
|
575
|
-
| `requiredPermissions` | `string[]`
|
|
576
|
-
| `requiredRole`
|
|
577
|
-
| `requireAll`
|
|
578
|
-
| `fallback`
|
|
579
|
-
| `onUnauthorized`
|
|
595
|
+
| Prop | Type | Default | Description |
|
|
596
|
+
| --------------------- | ------------ | ------- | ------------------------- |
|
|
597
|
+
| `requiredPermissions` | `string[]` | — | Required permissions |
|
|
598
|
+
| `requiredRole` | `string` | — | Required user role |
|
|
599
|
+
| `requireAll` | `boolean` | `true` | All permissions required? |
|
|
600
|
+
| `fallback` | `ReactNode` | `null` | Shown when access denied |
|
|
601
|
+
| `onUnauthorized` | `() => void` | — | Callback on denial |
|
|
580
602
|
|
|
581
603
|
---
|
|
582
604
|
|
|
@@ -648,7 +670,7 @@ import { Eye, EyeOff } from 'lucide-react';
|
|
|
648
670
|
showPassword: <Eye size={16} />,
|
|
649
671
|
hidePassword: <EyeOff size={16} />,
|
|
650
672
|
}}
|
|
651
|
-
|
|
673
|
+
/>;
|
|
652
674
|
```
|
|
653
675
|
|
|
654
676
|
---
|
|
@@ -657,14 +679,14 @@ import { Eye, EyeOff } from 'lucide-react';
|
|
|
657
679
|
|
|
658
680
|
### Core Providers
|
|
659
681
|
|
|
660
|
-
| Provider
|
|
661
|
-
|
|
662
|
-
| **AppProvider**
|
|
663
|
-
| **TenantProvider**
|
|
664
|
-
| **AuthProvider**
|
|
665
|
-
| **FeatureFlagProvider**
|
|
666
|
-
| **SubscriptionProvider** | Billing and subscription handling
|
|
667
|
-
| **RoutingProvider**
|
|
682
|
+
| Provider | Purpose |
|
|
683
|
+
| ------------------------ | ------------------------------------------ |
|
|
684
|
+
| **AppProvider** | Application configuration (baseUrl, appId) |
|
|
685
|
+
| **TenantProvider** | Multi-tenant detection and management |
|
|
686
|
+
| **AuthProvider** | Authentication, session, and user data |
|
|
687
|
+
| **FeatureFlagProvider** | Feature flag management |
|
|
688
|
+
| **SubscriptionProvider** | Billing and subscription handling |
|
|
689
|
+
| **RoutingProvider** | Zone-based routing (RFC-005) |
|
|
668
690
|
|
|
669
691
|
### Permission System
|
|
670
692
|
|
|
@@ -683,30 +705,38 @@ reports:read - View reports
|
|
|
683
705
|
- [🔧 Advanced Usage](./docs/advanced-usage.md)
|
|
684
706
|
- [📋 API Reference](./docs/api-reference.md)
|
|
685
707
|
- [🎯 Examples](./docs/examples.md)
|
|
686
|
-
- [✨ Magic Link Guide](./
|
|
708
|
+
- [✨ Magic Link Guide](./docs/magic-link-usage.md)
|
|
687
709
|
- [🛣️ Zone Routing](./docs/ZONE_ROUTING.md)
|
|
710
|
+
- [🔒 Security & Threat Model](./docs/security.md)
|
|
711
|
+
- [🚚 Migration to v2.31](./docs/migration-v2.31.md)
|
|
688
712
|
- [🤝 Contributing](./docs/contributing.md)
|
|
689
713
|
|
|
690
|
-
## 🎮
|
|
714
|
+
## 🎮 Example Application
|
|
715
|
+
|
|
716
|
+
A complete example application is included in the `example/` directory. It's a Vite-based React app with **two areas**:
|
|
691
717
|
|
|
692
|
-
|
|
718
|
+
- **`/demo/*`** — real feature pages (login, signup, magic link, tenant switching, subscription, zone routing, cookie session, standalone auth).
|
|
719
|
+
- **`/labs/*`** — inspection labs for the internal state of each provider and service (useful while developing against the library).
|
|
693
720
|
|
|
694
721
|
```bash
|
|
695
722
|
cd example
|
|
696
723
|
yarn install
|
|
697
|
-
yarn
|
|
724
|
+
yarn dev
|
|
698
725
|
```
|
|
699
726
|
|
|
700
|
-
The
|
|
701
|
-
|
|
702
|
-
- **
|
|
703
|
-
- **
|
|
704
|
-
- **
|
|
705
|
-
- **
|
|
706
|
-
- **
|
|
707
|
-
- **
|
|
708
|
-
- **
|
|
709
|
-
- **
|
|
727
|
+
The example showcases:
|
|
728
|
+
|
|
729
|
+
- **Traditional Authentication** — email/phone + password login
|
|
730
|
+
- **Magic Link Authentication** — passwordless login with automatic verification
|
|
731
|
+
- **User Registration** — signup with email/phone support
|
|
732
|
+
- **Password Recovery** — request + reset flows
|
|
733
|
+
- **Role-based Dashboard** — different views based on user roles
|
|
734
|
+
- **Permission Testing** — interactive `Protected` / `ProtectedRoute` demos
|
|
735
|
+
- **Zone Routing** — declarative route access control (RFC-005)
|
|
736
|
+
- **Feature Flags** — dynamic feature toggling
|
|
737
|
+
- **Multi-tenant Support** — tenant switching and management
|
|
738
|
+
- **Standalone AuthProvider** — auth-only setup without App/Tenant providers
|
|
739
|
+
- **Cross-subdomain Cookie Session** — `enableCookieSession` walkthrough
|
|
710
740
|
|
|
711
741
|
## 🛠️ Development
|
|
712
742
|
|
|
@@ -769,16 +799,49 @@ REACT_APP_TENANT_MODE=subdomain
|
|
|
769
799
|
```tsx
|
|
770
800
|
// AppProvider Config
|
|
771
801
|
interface AppConfig {
|
|
772
|
-
baseUrl: string;
|
|
773
|
-
appId: string;
|
|
802
|
+
baseUrl: string; // API base URL
|
|
803
|
+
appId: string; // Application identifier
|
|
804
|
+
cache?: {
|
|
805
|
+
enabled?: boolean; // Default: true
|
|
806
|
+
ttl?: number; // Cache TTL in ms, default: 5 min
|
|
807
|
+
storageKey?: string; // Default: `app_cache_{appId}`
|
|
808
|
+
};
|
|
774
809
|
}
|
|
775
810
|
|
|
776
811
|
// TenantProvider Config
|
|
777
812
|
interface TenantConfig {
|
|
778
|
-
tenantMode
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
813
|
+
tenantMode?: 'subdomain' | 'selector' | 'fixed';
|
|
814
|
+
fixedTenantSlug?: string; // Required when tenantMode is 'fixed'
|
|
815
|
+
baseDomain?: string; // Base domain for subdomain mode (e.g. 'acme.app')
|
|
816
|
+
selectorParam?: string; // Default: 'tenant', used in 'selector' mode
|
|
817
|
+
cache?: {
|
|
818
|
+
enabled?: boolean; // Default: true
|
|
819
|
+
ttl?: number; // Cache TTL in ms, default: 5 min
|
|
820
|
+
storageKey?: string; // Default: `tenant_cache_{tenantSlug}`
|
|
821
|
+
};
|
|
822
|
+
initialTenant?: PublicTenantInfo; // SSR support
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// AuthProvider Config (all fields optional)
|
|
826
|
+
interface AuthConfig {
|
|
827
|
+
// Standalone mode — required if AppProvider is not used
|
|
828
|
+
baseUrl?: string;
|
|
829
|
+
appId?: string;
|
|
830
|
+
|
|
831
|
+
// Session lifecycle
|
|
832
|
+
onSessionExpired?: (error: SessionExpiredError) => void;
|
|
833
|
+
refreshQueueTimeout?: number; // Default: 10_000 ms
|
|
834
|
+
proactiveRefreshMargin?: number; // Default: 60_000 ms before expiry
|
|
835
|
+
|
|
836
|
+
// Cross-subdomain cookie session (see "Security & Resilience" below)
|
|
837
|
+
enableCookieSession?: boolean; // Default: false
|
|
838
|
+
|
|
839
|
+
// Multi-tenant UX
|
|
840
|
+
autoSwitchSingleTenant?: boolean; // Auto-switch when user has exactly one tenant
|
|
841
|
+
onTenantSelectionRequired?: (tenants: UserTenantMembership[]) => void;
|
|
842
|
+
|
|
843
|
+
// Initial roles (avoid extra fetch on mount)
|
|
844
|
+
initialRoles?: Role[];
|
|
782
845
|
}
|
|
783
846
|
```
|
|
784
847
|
|
|
@@ -802,13 +865,37 @@ yarn test:coverage
|
|
|
802
865
|
- **Optimized re-renders** - Minimal React re-renders
|
|
803
866
|
- **Caching** - Intelligent caching of API responses
|
|
804
867
|
|
|
805
|
-
## 🔒 Security
|
|
868
|
+
## 🔒 Security & Resilience
|
|
869
|
+
|
|
870
|
+
### Token lifecycle
|
|
871
|
+
|
|
872
|
+
- **JWT access tokens** with automatic refresh and proactive renewal (60s margin by default).
|
|
873
|
+
- **Refresh queueing** — concurrent requests during refresh share a single in-flight request; no duplicate rotations.
|
|
874
|
+
- **Session generation tracking** — refresh attempts issued before a logout are discarded, preventing stale tokens from resurrecting a session.
|
|
875
|
+
- **Circuit breaker** — three consecutive background refresh failures transition the session to expired, preventing infinite retry loops.
|
|
876
|
+
- **Token reuse / revocation** — classified as fatal; session is cleared immediately to contain rotation-chain compromise.
|
|
877
|
+
|
|
878
|
+
### Multi-tab safety
|
|
879
|
+
|
|
880
|
+
The `SessionManager` is a singleton per `(baseUrl, tenantSlug)` pair and coordinates across tabs using the **Web Locks API**. Two tabs opening at the same time cannot issue duplicate refresh calls, and after a refresh all tabs converge on the same access token via shared storage.
|
|
881
|
+
|
|
882
|
+
### Cross-subdomain cookie session (`enableCookieSession`)
|
|
883
|
+
|
|
884
|
+
For SSO between subdomains (e.g. `www.example.com` ↔ `app.example.com`), set `enableCookieSession: true` in `AuthConfig`. When enabled:
|
|
885
|
+
|
|
886
|
+
- Refresh requests are sent with `credentials: 'include'`, so the backend's HttpOnly refresh cookie flows automatically.
|
|
887
|
+
- On app load, `SessionManager` attempts a cookie-based restore before requiring interactive login.
|
|
888
|
+
- No refresh token is ever written to `localStorage` — the access token lives in memory, the refresh token lives only in the HttpOnly cookie.
|
|
889
|
+
|
|
890
|
+
This replaces the previous, insecure `_auth=<token>` URL transfer mechanism (removed in **v2.31**). See [Migration to v2.31](./docs/migration-v2.31.md) for details.
|
|
891
|
+
|
|
892
|
+
### Permissions
|
|
893
|
+
|
|
894
|
+
- `resource:action` format, validated on both client and server.
|
|
895
|
+
- `Protected` / `ProtectedRoute` / `ZoneRoute` components block rendering when permissions are missing.
|
|
896
|
+
- Console output is suppressed in production and test environments.
|
|
806
897
|
|
|
807
|
-
|
|
808
|
-
- **Secure token storage** with configurable backends
|
|
809
|
-
- **Session generation tracking** to prevent stale token usage
|
|
810
|
-
- **Permission validation** on both client and server
|
|
811
|
-
- **Console output suppressed** in production and test environments
|
|
898
|
+
For a full threat model and list of fixed vulnerabilities, see [docs/security.md](./docs/security.md).
|
|
812
899
|
|
|
813
900
|
## 🌐 Browser Support
|
|
814
901
|
|