@jasperoosthoek/zustand-auth-registry 0.0.1
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/LICENSE +21 -0
- package/README.md +407 -0
- package/dist/authConfig.d.ts +67 -0
- package/dist/authStore.d.ts +17 -0
- package/dist/createAuthRegistry.d.ts +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/setupTests.d.ts +1 -0
- package/dist/useAuth.d.ts +7 -0
- package/package.json +50 -0
- package/src/__tests__/authConfig.test.ts +463 -0
- package/src/__tests__/authStore.test.ts +608 -0
- package/src/__tests__/createAuthRegistry.test.ts +202 -0
- package/src/__tests__/testHelpers.ts +92 -0
- package/src/__tests__/testUtils.ts +142 -0
- package/src/__tests__/useAuth.test.ts +975 -0
- package/src/authConfig.ts +184 -0
- package/src/authStore.ts +160 -0
- package/src/createAuthRegistry.ts +22 -0
- package/src/index.ts +4 -0
- package/src/setupTests.ts +46 -0
- package/src/useAuth.ts +157 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 jasperoosthoek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# zustand-auth-registry
|
|
2
|
+
|
|
3
|
+
OAuth 2.0 compliant authentication state management library using Zustand and Axios with a type-safe registry pattern.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`zustand-auth-registry` provides a simple, type-safe way to manage authentication state in React applications using Zustand. It supports both modern OAuth 2.0 standards and legacy authentication patterns, with automatic token refresh, Bearer token support, and comprehensive backward compatibility.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **OAuth 2.0 Compliance** - Industry-standard Bearer tokens, automatic refresh, token lifecycle management
|
|
12
|
+
- **Authentication State Management** - User, token, and authentication status with reactive updates
|
|
13
|
+
- **Registry Pattern** - Type-safe multiple auth stores per application
|
|
14
|
+
- **Axios Integration** - Automatic authentication header management with configurable formats
|
|
15
|
+
- **Token Lifecycle** - Automatic expiration detection, refresh workflows, and cleanup
|
|
16
|
+
- **Auto-Refresh** - Configurable threshold-based token renewal (default: 5 minutes before expiry)
|
|
17
|
+
- **Persistence** - Flexible storage options (localStorage, sessionStorage, custom) with OAuth token support
|
|
18
|
+
- **Type-Safe** - Full TypeScript support with generics for user models
|
|
19
|
+
- **Backward Compatible** - Seamless support for Django REST Framework and legacy APIs
|
|
20
|
+
- **Flexible** - Support for multiple APIs with different authentication strategies
|
|
21
|
+
- **Lightweight** - Simple API, no unnecessary complexity
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @jasperoosthoek/zustand-auth-registry zustand axios react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### OAuth 2.0 Setup (Recommended)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import axios from 'axios';
|
|
35
|
+
import { createAuthRegistry, useAuth } from '@jasperoosthoek/zustand-auth-registry';
|
|
36
|
+
|
|
37
|
+
// 1. Define your user type
|
|
38
|
+
type User = {
|
|
39
|
+
id: string;
|
|
40
|
+
email: string;
|
|
41
|
+
name: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// 2. Create registry
|
|
45
|
+
const getAuthStore = createAuthRegistry<{
|
|
46
|
+
main: User;
|
|
47
|
+
}>();
|
|
48
|
+
|
|
49
|
+
// 3. Create axios instance
|
|
50
|
+
const api = axios.create({ baseURL: 'https://api.example.com' });
|
|
51
|
+
|
|
52
|
+
// 4. Create OAuth 2.0 compliant auth store
|
|
53
|
+
export const authStore = getAuthStore('main', {
|
|
54
|
+
axios: api,
|
|
55
|
+
tokenUrl: '/oauth/token',
|
|
56
|
+
userInfoUrl: '/oauth/userinfo',
|
|
57
|
+
// Automatic OAuth token extraction (access_token, refresh_token, expires_in)
|
|
58
|
+
// Automatic Bearer header format
|
|
59
|
+
// Auto-refresh enabled by default
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 5. Use in components
|
|
63
|
+
function LoginForm() {
|
|
64
|
+
const { login } = useAuth(authStore);
|
|
65
|
+
const { user, isAuthenticated } = authStore((s) => s);
|
|
66
|
+
|
|
67
|
+
const handleLogin = async () => {
|
|
68
|
+
await login({
|
|
69
|
+
username: 'user@example.com',
|
|
70
|
+
password: 'password'
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (isAuthenticated) {
|
|
75
|
+
return <div>Welcome {user?.name}!</div>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return <button onClick={handleLogin}>Login</button>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Legacy/Django Setup (Backward Compatible)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Works with existing Django REST Framework patterns
|
|
86
|
+
export const authStore = getAuthStore('main', {
|
|
87
|
+
axios: api,
|
|
88
|
+
loginUrl: '/api/token/login/', // Legacy endpoint
|
|
89
|
+
logoutUrl: '/api/token/logout/',
|
|
90
|
+
getUserUrl: '/api/users/me/',
|
|
91
|
+
extractToken: (data) => data.auth_token, // Django field name
|
|
92
|
+
formatAuthHeader: (token) => `Token ${token}`, // Django format
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Use Cases
|
|
97
|
+
|
|
98
|
+
### OAuth 2.0 Provider Integration
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Works with Auth0, Google, GitHub, or any OAuth 2.0 provider
|
|
102
|
+
const auth0Store = getAuthStore('auth0', {
|
|
103
|
+
axios: api,
|
|
104
|
+
tokenUrl: 'https://your-domain.auth0.com/oauth/token',
|
|
105
|
+
userInfoUrl: 'https://your-domain.auth0.com/userinfo',
|
|
106
|
+
autoRefresh: true,
|
|
107
|
+
refreshThreshold: 300000, // Refresh 5 minutes before expiry
|
|
108
|
+
onTokenRefresh: (tokens) => {
|
|
109
|
+
console.log('Token refreshed, expires at:', new Date(tokens.expiresAt));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### JWT Token Support
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Automatic JWT expiration detection
|
|
118
|
+
const jwtStore = getAuthStore('jwt', {
|
|
119
|
+
axios: api,
|
|
120
|
+
tokenUrl: '/api/auth/login',
|
|
121
|
+
extractTokens: (data) => {
|
|
122
|
+
const payload = JSON.parse(atob(data.access_token.split('.')[1]));
|
|
123
|
+
return {
|
|
124
|
+
accessToken: data.access_token,
|
|
125
|
+
expiresAt: payload.exp * 1000, // JWT exp is in seconds
|
|
126
|
+
tokenType: 'Bearer'
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Multiple APIs with Different Authentication
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const internalApi = axios.create({ baseURL: 'https://internal.app.com' });
|
|
136
|
+
const partnerApi = axios.create({ baseURL: 'https://partner.api.com' });
|
|
137
|
+
|
|
138
|
+
// Internal API uses legacy Token authentication
|
|
139
|
+
const internalAuth = getAuthStore('internal', {
|
|
140
|
+
axios: internalApi,
|
|
141
|
+
loginUrl: '/api/token/login/',
|
|
142
|
+
extractToken: (data) => data.auth_token,
|
|
143
|
+
formatAuthHeader: (token) => `Token ${token}`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Partner API uses OAuth 2.0 Bearer authentication
|
|
147
|
+
const partnerAuth = getAuthStore('partner', {
|
|
148
|
+
axios: partnerApi,
|
|
149
|
+
tokenUrl: '/oauth/token',
|
|
150
|
+
userInfoUrl: '/oauth/userinfo',
|
|
151
|
+
// Uses Bearer tokens and auto-refresh by default
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Custom Storage Configuration
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const authStore = getAuthStore('main', {
|
|
159
|
+
axios: api,
|
|
160
|
+
tokenUrl: '/oauth/token',
|
|
161
|
+
persistence: {
|
|
162
|
+
enabled: true,
|
|
163
|
+
storage: sessionStorage, // Use sessionStorage instead of localStorage
|
|
164
|
+
tokenKey: 'access_token', // OAuth standard (default)
|
|
165
|
+
refreshTokenKey: 'refresh_token',
|
|
166
|
+
userKey: 'user_profile',
|
|
167
|
+
expiryKey: 'token_expires_at',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SSR/No Persistence
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const authStore = getAuthStore('main', {
|
|
176
|
+
axios: api,
|
|
177
|
+
tokenUrl: '/oauth/token',
|
|
178
|
+
persistence: {
|
|
179
|
+
enabled: false, // Disable persistence for SSR
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Integration with zustand-crud-registry
|
|
185
|
+
|
|
186
|
+
Works seamlessly with [@jasperoosthoek/zustand-crud-registry](https://github.com/jasperoosthoek/zustand-crud-registry):
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { createStoreRegistry } from '@jasperoosthoek/zustand-crud-registry';
|
|
190
|
+
import { createAuthRegistry } from '@jasperoosthoek/zustand-auth-registry';
|
|
191
|
+
|
|
192
|
+
// Shared axios instance
|
|
193
|
+
const api = axios.create({ baseURL: 'https://api.example.com' });
|
|
194
|
+
|
|
195
|
+
// Auth manages authentication with auto-refresh
|
|
196
|
+
const auth = getAuthStore('main', {
|
|
197
|
+
axios: api,
|
|
198
|
+
tokenUrl: '/oauth/token',
|
|
199
|
+
autoRefresh: true
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// CRUD uses the same authenticated axios
|
|
203
|
+
const getCrudStore = createStoreRegistry<{ user: User; post: Post }>();
|
|
204
|
+
const users = getCrudStore('user', { axios: api, route: '/users' });
|
|
205
|
+
|
|
206
|
+
// Login first, then use CRUD
|
|
207
|
+
const { login } = useAuth(auth);
|
|
208
|
+
const { list, getList } = useCrud(users);
|
|
209
|
+
|
|
210
|
+
await login({ username: '...', password: '...' });
|
|
211
|
+
await getList(); // Authenticated request with auto-refreshed tokens
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## API Reference
|
|
215
|
+
|
|
216
|
+
### `createAuthRegistry<Models>()`
|
|
217
|
+
|
|
218
|
+
Creates a registry function for type-safe auth stores.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
type Models = {
|
|
222
|
+
main: MainUser;
|
|
223
|
+
admin: AdminUser;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const getAuthStore = createAuthRegistry<Models>();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `getAuthStore(key, config)`
|
|
230
|
+
|
|
231
|
+
Creates or retrieves an auth store.
|
|
232
|
+
|
|
233
|
+
**Parameters:**
|
|
234
|
+
- `key`: Unique identifier for the store
|
|
235
|
+
- `config`: Authentication configuration
|
|
236
|
+
|
|
237
|
+
**Returns:** Auth store with Zustand state and config metadata
|
|
238
|
+
|
|
239
|
+
### `AuthConfig<U>`
|
|
240
|
+
|
|
241
|
+
Configuration object for authentication.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
type AuthConfig<U> = {
|
|
245
|
+
// Required
|
|
246
|
+
axios: AxiosInstance;
|
|
247
|
+
|
|
248
|
+
// OAuth 2.0 endpoints (recommended)
|
|
249
|
+
tokenUrl?: string; // POST /oauth/token
|
|
250
|
+
revokeUrl?: string; // POST /oauth/revoke
|
|
251
|
+
userInfoUrl?: string; // GET /oauth/userinfo
|
|
252
|
+
|
|
253
|
+
// Legacy endpoints (backward compatibility)
|
|
254
|
+
loginUrl?: string; // POST /api/token/login/
|
|
255
|
+
logoutUrl?: string; // POST /api/token/logout/
|
|
256
|
+
getUserUrl?: string; // GET /api/users/me/
|
|
257
|
+
|
|
258
|
+
// OAuth 2.0 token extraction (automatic if not specified)
|
|
259
|
+
extractTokens?: (data: any) => TokenData;
|
|
260
|
+
|
|
261
|
+
// Legacy token extraction (backward compatibility)
|
|
262
|
+
extractToken?: (data: any) => string;
|
|
263
|
+
|
|
264
|
+
// Token formatting (defaults to Bearer)
|
|
265
|
+
formatAuthHeader?: (token: string, tokenType?: string) => string;
|
|
266
|
+
|
|
267
|
+
// OAuth 2.0 features
|
|
268
|
+
autoRefresh?: boolean; // Default: true
|
|
269
|
+
refreshThreshold?: number; // Default: 300000ms (5 minutes)
|
|
270
|
+
|
|
271
|
+
// Storage configuration
|
|
272
|
+
persistence?: {
|
|
273
|
+
enabled?: boolean; // Default: true
|
|
274
|
+
storage?: Storage; // Default: localStorage
|
|
275
|
+
tokenKey?: string; // Default: 'token'
|
|
276
|
+
refreshTokenKey?: string; // Default: 'refresh_token'
|
|
277
|
+
userKey?: string; // Default: 'user'
|
|
278
|
+
expiryKey?: string; // Default: 'expires_at'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Event callbacks
|
|
282
|
+
onError?: (error: any) => void;
|
|
283
|
+
onLogin?: (user: U) => void;
|
|
284
|
+
onLogout?: () => void;
|
|
285
|
+
onTokenRefresh?: (tokens: TokenData) => void;
|
|
286
|
+
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### `TokenData`
|
|
290
|
+
|
|
291
|
+
OAuth 2.0 compliant token structure.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
type TokenData = {
|
|
295
|
+
accessToken: string; // OAuth 2.0 standard
|
|
296
|
+
refreshToken?: string; // For token renewal
|
|
297
|
+
expiresAt?: number; // Timestamp for expiration
|
|
298
|
+
tokenType: string; // 'Bearer' (default) or custom
|
|
299
|
+
scope?: string[]; // OAuth scope support
|
|
300
|
+
};
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### `useAuth(store)`
|
|
304
|
+
|
|
305
|
+
React hook for authentication actions.
|
|
306
|
+
|
|
307
|
+
**Returns:**
|
|
308
|
+
```typescript
|
|
309
|
+
{
|
|
310
|
+
login: (credentials: Record<string, string>, callback?: () => void) => Promise<void>;
|
|
311
|
+
logout: () => Promise<void>;
|
|
312
|
+
getCurrentUser: () => Promise<void>;
|
|
313
|
+
refreshTokens: () => Promise<boolean>; // OAuth 2.0 token refresh
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Auth Store State
|
|
318
|
+
|
|
319
|
+
Access auth state using the store:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const { user, token, tokens, isAuthenticated } = authStore((s) => s);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**State:**
|
|
326
|
+
- `user: U | null` - Current user object
|
|
327
|
+
- `token: string` - Authentication token (backward compatibility)
|
|
328
|
+
- `tokens: TokenData | null` - OAuth 2.0 token structure
|
|
329
|
+
- `isAuthenticated: boolean` - Whether user is authenticated (based on valid token)
|
|
330
|
+
|
|
331
|
+
**Actions:**
|
|
332
|
+
- `setToken(token: string)` - Set authentication token (backward compatibility)
|
|
333
|
+
- `setTokens(tokens: TokenData)` - Set OAuth 2.0 tokens
|
|
334
|
+
- `setUser(user: U)` - Set user object
|
|
335
|
+
- `unsetUser()` - Clear user and tokens (logout)
|
|
336
|
+
- `isTokenExpired(): boolean` - Check if current token is expired
|
|
337
|
+
|
|
338
|
+
## Migration Guide
|
|
339
|
+
|
|
340
|
+
### From Legacy to OAuth 2.0
|
|
341
|
+
|
|
342
|
+
**Step 1: No changes required (existing code continues to work)**
|
|
343
|
+
```typescript
|
|
344
|
+
// Existing configurations work unchanged
|
|
345
|
+
const authStore = getAuthStore('main', {
|
|
346
|
+
loginUrl: '/api/token/login/',
|
|
347
|
+
extractToken: (data) => data.auth_token,
|
|
348
|
+
formatAuthHeader: (token) => `Token ${token}`
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Step 2: Gradual OAuth adoption**
|
|
353
|
+
```typescript
|
|
354
|
+
// Start using OAuth endpoints while keeping legacy token extraction
|
|
355
|
+
const authStore = getAuthStore('main', {
|
|
356
|
+
tokenUrl: '/oauth/token', // OAuth endpoint
|
|
357
|
+
extractToken: (data) => data.auth_token, // Legacy extraction
|
|
358
|
+
formatAuthHeader: (token) => `Token ${token}` // Legacy headers
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Step 3: Full OAuth 2.0**
|
|
363
|
+
```typescript
|
|
364
|
+
// Complete OAuth 2.0 implementation
|
|
365
|
+
const authStore = getAuthStore('main', {
|
|
366
|
+
tokenUrl: '/oauth/token',
|
|
367
|
+
revokeUrl: '/oauth/revoke',
|
|
368
|
+
userInfoUrl: '/oauth/userinfo',
|
|
369
|
+
// OAuth token extraction and Bearer headers are automatic
|
|
370
|
+
autoRefresh: true,
|
|
371
|
+
refreshThreshold: 300000 // 5 minutes
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Development
|
|
376
|
+
|
|
377
|
+
See [SETUP.md](./SETUP.md) for detailed setup and development instructions.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
# Install dependencies
|
|
381
|
+
npm install
|
|
382
|
+
|
|
383
|
+
# Build
|
|
384
|
+
npm run build
|
|
385
|
+
|
|
386
|
+
# Test (67 tests, 100% pass rate)
|
|
387
|
+
npm test
|
|
388
|
+
|
|
389
|
+
# Coverage
|
|
390
|
+
npm run test:coverage
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## OAuth 2.0 Compliance
|
|
394
|
+
|
|
395
|
+
This library implements OAuth 2.0 (RFC 6749) standards with Bearer Token Authentication (RFC 6750), token refresh flows, proper token lifecycle management, and standard field names. For detailed implementation information and future roadmap, see [docs/OAUTH_ROADMAP.md](./docs/OAUTH_ROADMAP.md).
|
|
396
|
+
|
|
397
|
+
## Related Projects
|
|
398
|
+
|
|
399
|
+
- [@jasperoosthoek/zustand-crud-registry](https://github.com/jasperoosthoek/zustand-crud-registry) - CRUD operations for REST APIs
|
|
400
|
+
|
|
401
|
+
## License
|
|
402
|
+
|
|
403
|
+
MIT
|
|
404
|
+
|
|
405
|
+
## Author
|
|
406
|
+
|
|
407
|
+
Jasper Oosthoek
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
export type TokenData = {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
expiresAt?: number;
|
|
6
|
+
tokenType: string;
|
|
7
|
+
scope?: string[];
|
|
8
|
+
};
|
|
9
|
+
export type TokenExtractor = (data: any) => string | TokenData;
|
|
10
|
+
export type AuthConfig<U> = {
|
|
11
|
+
axios: AxiosInstance;
|
|
12
|
+
tokenUrl?: string;
|
|
13
|
+
revokeUrl?: string;
|
|
14
|
+
userInfoUrl?: string;
|
|
15
|
+
loginUrl?: string;
|
|
16
|
+
logoutUrl?: string;
|
|
17
|
+
getUserUrl?: string;
|
|
18
|
+
extractTokens?: (data: any) => TokenData;
|
|
19
|
+
extractAccessToken?: (data: any) => string;
|
|
20
|
+
extractRefreshToken?: (data: any) => string | undefined;
|
|
21
|
+
extractExpiresIn?: (data: any) => number | undefined;
|
|
22
|
+
extractTokenType?: (data: any) => string;
|
|
23
|
+
extractScope?: (data: any) => string[] | undefined;
|
|
24
|
+
extractToken?: (data: any) => string;
|
|
25
|
+
formatAuthHeader?: (token: string, tokenType?: string) => string;
|
|
26
|
+
autoRefresh?: boolean;
|
|
27
|
+
refreshThreshold?: number;
|
|
28
|
+
persistence?: {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
storage?: Storage;
|
|
31
|
+
tokenKey?: string;
|
|
32
|
+
refreshTokenKey?: string;
|
|
33
|
+
userKey?: string;
|
|
34
|
+
expiryKey?: string;
|
|
35
|
+
};
|
|
36
|
+
onError?: (error: any) => void;
|
|
37
|
+
onLogin?: (user: U) => void;
|
|
38
|
+
onLogout?: () => void;
|
|
39
|
+
onTokenRefresh?: (tokens: TokenData) => void;
|
|
40
|
+
};
|
|
41
|
+
export type ValidatedAuthConfig<U> = {
|
|
42
|
+
axios: AxiosInstance;
|
|
43
|
+
tokenUrl: string;
|
|
44
|
+
revokeUrl?: string;
|
|
45
|
+
userInfoUrl?: string;
|
|
46
|
+
loginUrl?: string;
|
|
47
|
+
logoutUrl?: string;
|
|
48
|
+
getUserUrl?: string;
|
|
49
|
+
extractTokens: (data: any) => TokenData;
|
|
50
|
+
extractToken?: (data: any) => string;
|
|
51
|
+
formatAuthHeader: (token: string, tokenType?: string) => string;
|
|
52
|
+
autoRefresh: boolean;
|
|
53
|
+
refreshThreshold: number;
|
|
54
|
+
persistence: {
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
storage: Storage;
|
|
57
|
+
tokenKey: string;
|
|
58
|
+
refreshTokenKey: string;
|
|
59
|
+
userKey: string;
|
|
60
|
+
expiryKey: string;
|
|
61
|
+
};
|
|
62
|
+
onError?: (error: any) => void;
|
|
63
|
+
onLogin?: (user: U) => void;
|
|
64
|
+
onLogout?: () => void;
|
|
65
|
+
onTokenRefresh?: (tokens: TokenData) => void;
|
|
66
|
+
};
|
|
67
|
+
export declare const validateAuthConfig: <U>(config: AuthConfig<U>) => ValidatedAuthConfig<U>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StoreApi, UseBoundStore } from 'zustand';
|
|
2
|
+
import { ValidatedAuthConfig, TokenData } from './authConfig';
|
|
3
|
+
export type AuthState<U> = {
|
|
4
|
+
isAuthenticated: boolean;
|
|
5
|
+
user: U | null;
|
|
6
|
+
tokens: TokenData | null;
|
|
7
|
+
setTokens: (tokens: TokenData) => void;
|
|
8
|
+
setUser: (user: U) => void;
|
|
9
|
+
unsetUser: () => void;
|
|
10
|
+
isTokenExpired: () => boolean;
|
|
11
|
+
token: string;
|
|
12
|
+
setToken: (token: string) => void;
|
|
13
|
+
};
|
|
14
|
+
export type AuthStore<U> = UseBoundStore<StoreApi<AuthState<U>>> & {
|
|
15
|
+
config: ValidatedAuthConfig<U>;
|
|
16
|
+
};
|
|
17
|
+
export declare const createAuthStore: <U>(config: ValidatedAuthConfig<U>) => AuthStore<U>;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,r){"object"==typeof exports&&"object"==typeof module?module.exports=r(require("zustand"),require("react"),require("axios")):"function"==typeof define&&define.amd?define(["zustand","react","axios"],r):"object"==typeof exports?exports["@jasperoosthoek/zustand-auth-registry"]=r(require("zustand"),require("react"),require("axios")):e["@jasperoosthoek/zustand-auth-registry"]=r(e.zustand,e.react,e.axios)}(this,(e,r,t)=>(()=>{"use strict";var n={155:e=>{e.exports=r},287:r=>{r.exports=e},742:e=>{e.exports=t}},o={};function s(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={exports:{}};return n[e](t,t.exports,s),t.exports}s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var t in r)s.o(r,t)&&!s.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var a={};s.r(a),s.d(a,{createAuthRegistry:()=>f,createAuthStore:()=>l,useAuth:()=>y,validateAuthConfig:()=>u});var i=function(){return i=Object.assign||function(e){for(var r,t=1,n=arguments.length;t<n;t++)for(var o in r=arguments[t])Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o]);return e},i.apply(this,arguments)},u=function(e){var r,t;if(!e.axios)throw new Error("AuthConfig: axios instance is required");var n=e.tokenUrl||e.loginUrl;if(!n)throw new Error("AuthConfig: tokenUrl or loginUrl is required");var o=e.revokeUrl,s=e.userInfoUrl||e.getUserUrl,a=function(e){return function(r){if(e.extractTokens)return e.extractTokens(r);if(r.access_token)return{accessToken:e.extractAccessToken?e.extractAccessToken(r):r.access_token,refreshToken:e.extractRefreshToken?e.extractRefreshToken(r):r.refresh_token,expiresAt:e.extractExpiresIn?e.extractExpiresIn(r)?Date.now()+1e3*e.extractExpiresIn(r):void 0:r.expires_in?Date.now()+1e3*r.expires_in:void 0,tokenType:e.extractTokenType?e.extractTokenType(r):r.token_type||"Bearer",scope:e.extractScope?e.extractScope(r):r.scope?r.scope.split(" "):void 0};if(e.extractToken||r.auth_token||r.token)return{accessToken:e.extractToken?e.extractToken(r):r.auth_token||r.token,tokenType:"Bearer"};throw new Error("No valid token found in response. Provide extractTokens, extractToken, or ensure response contains access_token/auth_token field.")}}(e),u={enabled:!0,storage:"undefined"!=typeof window&&window.localStorage?window.localStorage:{},tokenKey:"token",refreshTokenKey:"refresh_token",userKey:"user",expiryKey:"expires_at"};return{axios:e.axios,tokenUrl:n,revokeUrl:o,userInfoUrl:s,loginUrl:e.loginUrl,logoutUrl:e.logoutUrl,getUserUrl:e.getUserUrl,extractTokens:a,extractToken:e.extractToken,formatAuthHeader:e.formatAuthHeader||function(e,r){return void 0===r&&(r="Bearer"),"".concat(r," ").concat(e)},autoRefresh:null===(r=e.autoRefresh)||void 0===r||r,refreshThreshold:null!==(t=e.refreshThreshold)&&void 0!==t?t:3e5,persistence:i(i({},u),e.persistence),onError:e.onError,onLogin:e.onLogin,onLogout:e.onLogout,onTokenRefresh:e.onTokenRefresh}},c=s(287),l=function(e){var r=e.persistence,t=function(){if(!r.enabled)return null;try{var e=r.storage.getItem(r.tokenKey);if(!e)return null;var t=r.storage.getItem(r.refreshTokenKey),n=r.storage.getItem(r.expiryKey),o=n?parseInt(n,10):void 0;return{accessToken:e,refreshToken:t||void 0,expiresAt:o&&!isNaN(o)?o:void 0,tokenType:"Bearer"}}catch(e){return null}}(),n=function(){if(!r.enabled)return null;try{var e=r.storage.getItem(r.userKey);return e?JSON.parse(e):null}catch(e){return null}}(),o=!!(null==t?void 0:t.accessToken),s=(0,c.create)(function(s,a){return{tokens:t,user:n,isAuthenticated:o,setTokens:function(t){a().user;var n,o=!!t.accessToken;if(s({tokens:t,isAuthenticated:o,token:t.accessToken}),r.enabled)try{r.storage.setItem(r.tokenKey,t.accessToken),t.refreshToken?r.storage.setItem(r.refreshTokenKey,t.refreshToken):r.storage.removeItem(r.refreshTokenKey),t.expiresAt?r.storage.setItem(r.expiryKey,t.expiresAt.toString()):r.storage.removeItem(r.expiryKey)}catch(r){null===(n=e.onError)||void 0===n||n.call(e,r)}},setUser:function(t){var n,o=a().tokens,i=!!(null==o?void 0:o.accessToken);if(s({user:t,isAuthenticated:i}),r.enabled)try{r.storage.setItem(r.userKey,JSON.stringify(t))}catch(r){null===(n=e.onError)||void 0===n||n.call(e,r)}},unsetUser:function(){var t;if(s({user:null,tokens:null,isAuthenticated:!1,token:""}),r.enabled)try{r.storage.removeItem(r.tokenKey),r.storage.removeItem(r.refreshTokenKey),r.storage.removeItem(r.userKey),r.storage.removeItem(r.expiryKey)}catch(r){null===(t=e.onError)||void 0===t||t.call(e,r)}},isTokenExpired:function(){var e=a().tokens;return!!(null==e?void 0:e.expiresAt)&&Date.now()>=e.expiresAt},token:(null==t?void 0:t.accessToken)||"",setToken:function(t){var n,o=a().tokens,i=(a().user,{accessToken:t,refreshToken:null==o?void 0:o.refreshToken,expiresAt:null==o?void 0:o.expiresAt,tokenType:(null==o?void 0:o.tokenType)||"Bearer",scope:null==o?void 0:o.scope});if(s({tokens:i,token:t,isAuthenticated:!!t}),r.enabled)try{r.storage.setItem(r.tokenKey,t)}catch(r){null===(n=e.onError)||void 0===n||n.call(e,r)}}}});return Object.assign(s,{config:e})};function f(){var e={};return function(r,t){var n=String(r);if(!e[n]){var o=u(t);e[n]=l(o)}return e[n]}}var k=s(155),d=s(742),h=s.n(d),p=function(e,r,t,n){return new(t||(t=Promise))(function(o,s){function a(e){try{u(n.next(e))}catch(e){s(e)}}function i(e){try{u(n.throw(e))}catch(e){s(e)}}function u(e){var r;e.done?o(e.value):(r=e.value,r instanceof t?r:new t(function(e){e(r)})).then(a,i)}u((n=n.apply(e,r||[])).next())})},v=function(e,r){var t,n,o,s,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(i){return function(u){return function(i){if(t)throw new TypeError("Generator is already executing.");for(;s&&(s=0,i[0]&&(a=0)),a;)try{if(t=1,n&&(o=2&i[0]?n.return:i[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,i[1])).done)return o;switch(n=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,n=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]<o[3])){a.label=i[1];break}if(6===i[0]&&a.label<o[1]){a.label=o[1],o=i;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(i);break}o[2]&&a.ops.pop(),a.trys.pop();continue}i=r.call(e,a)}catch(e){i=[6,e],n=0}finally{t=o=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,u])}}};function y(e){var r=this,t=e(),n=t.setTokens,o=t.setUser,s=t.unsetUser,a=t.tokens,i=t.user,u=t.isTokenExpired,c=e.config,l=e(),f=(l.setToken,l.token,(0,k.useCallback)(function(){return p(r,void 0,void 0,function(){var e,r,t,o,i;return v(this,function(u){switch(u.label){case 0:if(!(null==a?void 0:a.refreshToken))return[2,!1];u.label=1;case 1:return u.trys.push([1,3,,4]),[4,c.axios.post(c.tokenUrl,{grant_type:"refresh_token",refresh_token:a.refreshToken})];case 2:return e=u.sent(),r=c.extractTokens(e.data),n(r),d(r.accessToken,r.tokenType),null===(o=c.onTokenRefresh)||void 0===o||o.call(c,r),[2,!0];case 3:return t=u.sent(),s(),d(),null===(i=c.onError)||void 0===i||i.call(c,t),[2,!1];case 4:return[2]}})})},[null==a?void 0:a.refreshToken,c,n,s]));(0,k.useEffect)(function(){var e,r;if(null==a?void 0:a.accessToken){if(d(a.accessToken,a.tokenType),u())return void(a.refreshToken&&c.autoRefresh?f():s());if(a.expiresAt&&a.refreshToken&&c.autoRefresh){var t=a.expiresAt-Date.now(),n=Math.max(t-c.refreshThreshold,0),o=setTimeout(function(){f()},n);return function(){return clearTimeout(o)}}try{i||!c.userInfoUrl&&!c.getUserUrl||y()}catch(t){h().isAxiosError(t)&&403===(null===(e=t.response)||void 0===e?void 0:e.status)&&(s(),d()),null===(r=c.onError)||void 0===r||r.call(c,t)}}},[a,i,c.autoRefresh,c.refreshThreshold,c.userInfoUrl,c.getUserUrl,u,f,s]);var d=function(e,r){void 0!==e&&e?c.axios.defaults.headers.common.Authorization=c.formatAuthHeader(e,r):delete c.axios.defaults.headers.common.Authorization},y=function(){return p(r,void 0,void 0,function(){var e,r,t,n;return v(this,function(a){switch(a.label){case 0:if(!(e=c.userInfoUrl||c.getUserUrl))return[2];a.label=1;case 1:return a.trys.push([1,3,,4]),[4,c.axios.get(e)];case 2:return r=a.sent(),o(r.data),[3,4];case 3:return t=a.sent(),s(),d(),null===(n=c.onError)||void 0===n||n.call(c,t),[3,4];case 4:return[2]}})})};return{login:function(e,t){return p(r,void 0,void 0,function(){var r,o,a,u,l;return v(this,function(f){switch(f.label){case 0:return f.trys.push([0,4,,5]),[4,c.axios.post(c.tokenUrl,e)];case 1:return r=f.sent(),o=c.extractTokens(r.data),n(o),d(o.accessToken,o.tokenType),c.userInfoUrl||c.getUserUrl?[4,y()]:[3,3];case 2:f.sent(),f.label=3;case 3:return i&&(null===(u=c.onLogin)||void 0===u||u.call(c,i)),null==t||t(),[3,5];case 4:return a=f.sent(),s(),d(),null===(l=c.onError)||void 0===l||l.call(c,a),[3,5];case 5:return[2]}})})},getCurrentUser:y,logout:function(){return p(r,void 0,void 0,function(){var e,r,t,n;return v(this,function(o){switch(o.label){case 0:return o.trys.push([0,5,6,7]),(e=c.revokeUrl||c.logoutUrl)?c.revokeUrl&&(null==a?void 0:a.refreshToken)?[4,c.axios.post(e,{token:a.refreshToken,token_type_hint:"refresh_token"})]:[3,2]:[3,4];case 1:return o.sent(),[3,4];case 2:return[4,c.axios.post(e)];case 3:o.sent(),o.label=4;case 4:return[3,7];case 5:return r=o.sent(),null===(t=c.onError)||void 0===t||t.call(c,r),[3,7];case 6:return s(),d(),null===(n=c.onLogout)||void 0===n||n.call(c),[7];case 7:return[2]}})})},refreshTokens:f}}return a})());
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","mappings":"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","sources":["webpack://@jasperoosthoek/zustand-auth-registry/webpack/universalModuleDefinition","webpack://@jasperoosthoek/zustand-auth-registry/external umd \"react\"","webpack://@jasperoosthoek/zustand-auth-registry/external umd \"zustand\"","webpack://@jasperoosthoek/zustand-auth-registry/external umd \"axios\"","webpack://@jasperoosthoek/zustand-auth-registry/webpack/bootstrap","webpack://@jasperoosthoek/zustand-auth-registry/webpack/runtime/compat get default export","webpack://@jasperoosthoek/zustand-auth-registry/webpack/runtime/define property getters","webpack://@jasperoosthoek/zustand-auth-registry/webpack/runtime/hasOwnProperty shorthand","webpack://@jasperoosthoek/zustand-auth-registry/webpack/runtime/make namespace object","webpack://@jasperoosthoek/zustand-auth-registry/./src/authConfig.ts","webpack://@jasperoosthoek/zustand-auth-registry/./src/authStore.ts","webpack://@jasperoosthoek/zustand-auth-registry/./src/createAuthRegistry.ts","webpack://@jasperoosthoek/zustand-auth-registry/./src/useAuth.ts"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"zustand\"), require(\"react\"), require(\"axios\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"zustand\", \"react\", \"axios\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"@jasperoosthoek/zustand-auth-registry\"] = factory(require(\"zustand\"), require(\"react\"), require(\"axios\"));\n\telse\n\t\troot[\"@jasperoosthoek/zustand-auth-registry\"] = factory(root[\"zustand\"], root[\"react\"], root[\"axios\"]);\n})(this, (__WEBPACK_EXTERNAL_MODULE__287__, __WEBPACK_EXTERNAL_MODULE__155__, __WEBPACK_EXTERNAL_MODULE__742__) => {\nreturn ","module.exports = __WEBPACK_EXTERNAL_MODULE__155__;","module.exports = __WEBPACK_EXTERNAL_MODULE__287__;","module.exports = __WEBPACK_EXTERNAL_MODULE__742__;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { AxiosInstance } from 'axios';\n\n// OAuth 2.0 token data structure\nexport type TokenData = {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType: string;\n scope?: string[];\n};\n\n// Backward compatibility: extract single token vs OAuth token data\nexport type TokenExtractor = (data: any) => string | TokenData;\n\nexport type AuthConfig<U> = {\n axios: AxiosInstance;\n \n // OAuth 2.0 compliant endpoints (new defaults)\n tokenUrl?: string;\n revokeUrl?: string;\n userInfoUrl?: string;\n \n // Backward compatibility: Django-style endpoints\n loginUrl?: string;\n logoutUrl?: string;\n getUserUrl?: string;\n \n // OAuth 2.0 compliant token extraction\n extractTokens?: (data: any) => TokenData;\n extractAccessToken?: (data: any) => string;\n extractRefreshToken?: (data: any) => string | undefined;\n extractExpiresIn?: (data: any) => number | undefined;\n extractTokenType?: (data: any) => string;\n extractScope?: (data: any) => string[] | undefined;\n \n // Backward compatibility: single token extraction\n extractToken?: (data: any) => string;\n \n formatAuthHeader?: (token: string, tokenType?: string) => string;\n \n // OAuth 2.0 features\n autoRefresh?: boolean;\n refreshThreshold?: number; // ms before expiry to refresh\n \n persistence?: {\n enabled?: boolean;\n storage?: Storage;\n tokenKey?: string;\n refreshTokenKey?: string;\n userKey?: string;\n expiryKey?: string;\n };\n \n onError?: (error: any) => void;\n onLogin?: (user: U) => void;\n onLogout?: () => void;\n onTokenRefresh?: (tokens: TokenData) => void;\n};\n\nexport type ValidatedAuthConfig<U> = {\n axios: AxiosInstance;\n \n // Resolved endpoints (OAuth preferred, Django fallback)\n tokenUrl: string;\n revokeUrl?: string;\n userInfoUrl?: string;\n \n // Backward compatibility endpoints\n loginUrl?: string;\n logoutUrl?: string;\n getUserUrl?: string;\n \n // Token extraction functions\n extractTokens: (data: any) => TokenData;\n extractToken?: (data: any) => string;\n \n formatAuthHeader: (token: string, tokenType?: string) => string;\n \n // OAuth features\n autoRefresh: boolean;\n refreshThreshold: number;\n \n persistence: {\n enabled: boolean;\n storage: Storage;\n tokenKey: string;\n refreshTokenKey: string;\n userKey: string;\n expiryKey: string;\n };\n \n onError?: (error: any) => void;\n onLogin?: (user: U) => void;\n onLogout?: () => void;\n onTokenRefresh?: (tokens: TokenData) => void;\n};\n\nexport const validateAuthConfig = <U>(config: AuthConfig<U>): ValidatedAuthConfig<U> => {\n if (!config.axios) {\n throw new Error('AuthConfig: axios instance is required');\n }\n\n // Determine token endpoint (OAuth preferred, Django fallback)\n const tokenUrl = config.tokenUrl || config.loginUrl;\n if (!tokenUrl) {\n throw new Error('AuthConfig: tokenUrl or loginUrl is required');\n }\n\n // Keep revokeUrl and logoutUrl distinct for proper OAuth/legacy behavior\n const revokeUrl = config.revokeUrl;\n\n // Determine user info endpoint\n const userInfoUrl = config.userInfoUrl || config.getUserUrl;\n\n // Create OAuth-compliant token extraction function\n const extractTokens = createTokenExtractor(config);\n\n const defaultPersistence = {\n enabled: true,\n storage: typeof window !== 'undefined' && window.localStorage ? window.localStorage : ({} as Storage),\n tokenKey: 'token',\n refreshTokenKey: 'refresh_token',\n userKey: 'user',\n expiryKey: 'expires_at',\n };\n\n return {\n axios: config.axios,\n tokenUrl,\n revokeUrl,\n userInfoUrl,\n // Backward compatibility\n loginUrl: config.loginUrl,\n logoutUrl: config.logoutUrl,\n getUserUrl: config.getUserUrl,\n extractTokens,\n extractToken: config.extractToken,\n formatAuthHeader: config.formatAuthHeader || ((token: string, tokenType: string = 'Bearer') => `${tokenType} ${token}`),\n autoRefresh: config.autoRefresh ?? true,\n refreshThreshold: config.refreshThreshold ?? 300000, // 5 minutes\n persistence: {\n ...defaultPersistence,\n ...config.persistence,\n },\n onError: config.onError,\n onLogin: config.onLogin,\n onLogout: config.onLogout,\n onTokenRefresh: config.onTokenRefresh,\n };\n};\n\n// Helper function to create OAuth-compliant token extractor with backward compatibility\nfunction createTokenExtractor<U>(config: AuthConfig<U>): (data: any) => TokenData {\n return (data: any): TokenData => {\n // If custom extractTokens function provided, use it\n if (config.extractTokens) {\n return config.extractTokens(data);\n }\n\n // OAuth 2.0 compliant extraction (preferred)\n if (data.access_token) {\n return {\n accessToken: config.extractAccessToken ? config.extractAccessToken(data) : data.access_token,\n refreshToken: config.extractRefreshToken ? config.extractRefreshToken(data) : data.refresh_token,\n expiresAt: config.extractExpiresIn \n ? (config.extractExpiresIn(data) ? Date.now() + (config.extractExpiresIn(data)! * 1000) : undefined)\n : (data.expires_in ? Date.now() + (data.expires_in * 1000) : undefined),\n tokenType: config.extractTokenType ? config.extractTokenType(data) : (data.token_type || 'Bearer'),\n scope: config.extractScope ? config.extractScope(data) : (data.scope ? data.scope.split(' ') : undefined),\n };\n }\n\n // Single token fallback (backward compatibility)\n if (config.extractToken || data.auth_token || data.token) {\n const token = config.extractToken ? config.extractToken(data) : (data.auth_token || data.token);\n return {\n accessToken: token,\n tokenType: 'Bearer', // Standard default\n };\n }\n\n throw new Error('No valid token found in response. Provide extractTokens, extractToken, or ensure response contains access_token/auth_token field.');\n };\n}","import { create, StoreApi, UseBoundStore } from 'zustand';\nimport { ValidatedAuthConfig, TokenData } from './authConfig';\n\nexport type AuthState<U> = {\n isAuthenticated: boolean;\n user: U | null;\n tokens: TokenData | null;\n \n // OAuth 2.0 methods\n setTokens: (tokens: TokenData) => void;\n setUser: (user: U) => void;\n unsetUser: () => void;\n isTokenExpired: () => boolean;\n \n // Backward compatibility\n token: string;\n setToken: (token: string) => void;\n};\n\nexport type AuthStore<U> = UseBoundStore<StoreApi<AuthState<U>>> & {\n config: ValidatedAuthConfig<U>;\n};\n\nexport const createAuthStore = <U>(config: ValidatedAuthConfig<U>): AuthStore<U> => {\n const { persistence } = config;\n \n const getStoredTokens = (): TokenData | null => {\n if (!persistence.enabled) return null;\n try {\n const accessToken = persistence.storage.getItem(persistence.tokenKey);\n if (!accessToken) return null;\n \n const refreshToken = persistence.storage.getItem(persistence.refreshTokenKey);\n const expiryString = persistence.storage.getItem(persistence.expiryKey);\n const expiresAt = expiryString ? parseInt(expiryString, 10) : undefined;\n \n return {\n accessToken,\n refreshToken: refreshToken || undefined,\n expiresAt: expiresAt && !isNaN(expiresAt) ? expiresAt : undefined,\n tokenType: 'Bearer', // Default, will be updated on setTokens\n };\n } catch {\n return null;\n }\n };\n\n const getStoredUser = (): U | null => {\n if (!persistence.enabled) return null;\n try {\n const userString = persistence.storage.getItem(persistence.userKey);\n return userString ? (JSON.parse(userString) as U) : null;\n } catch {\n return null;\n }\n };\n\n const initialTokens = getStoredTokens();\n const initialUser = getStoredUser();\n const initialIsAuthenticated = !!initialTokens?.accessToken;\n\n const store = create<AuthState<U>>((set, get) => ({\n tokens: initialTokens,\n user: initialUser,\n isAuthenticated: initialIsAuthenticated,\n\n // OAuth 2.0 methods\n setTokens: (tokens: TokenData) => {\n const user = get().user;\n const isAuthenticated = !!tokens.accessToken;\n \n set({ tokens, isAuthenticated, token: tokens.accessToken });\n \n if (persistence.enabled) {\n try {\n persistence.storage.setItem(persistence.tokenKey, tokens.accessToken);\n \n if (tokens.refreshToken) {\n persistence.storage.setItem(persistence.refreshTokenKey, tokens.refreshToken);\n } else {\n persistence.storage.removeItem(persistence.refreshTokenKey);\n }\n \n if (tokens.expiresAt) {\n persistence.storage.setItem(persistence.expiryKey, tokens.expiresAt.toString());\n } else {\n persistence.storage.removeItem(persistence.expiryKey);\n }\n } catch (error) {\n config.onError?.(error);\n }\n }\n },\n\n setUser: (user: U) => {\n const tokens = get().tokens;\n const isAuthenticated = !!tokens?.accessToken;\n \n set({ user, isAuthenticated });\n \n if (persistence.enabled) {\n try {\n persistence.storage.setItem(persistence.userKey, JSON.stringify(user));\n } catch (error) {\n config.onError?.(error);\n }\n }\n },\n\n unsetUser: () => {\n set({ user: null, tokens: null, isAuthenticated: false, token: '' });\n \n if (persistence.enabled) {\n try {\n persistence.storage.removeItem(persistence.tokenKey);\n persistence.storage.removeItem(persistence.refreshTokenKey);\n persistence.storage.removeItem(persistence.userKey);\n persistence.storage.removeItem(persistence.expiryKey);\n } catch (error) {\n config.onError?.(error);\n }\n }\n },\n\n isTokenExpired: () => {\n const tokens = get().tokens;\n if (!tokens?.expiresAt) return false; // No expiry info means no expiration\n return Date.now() >= tokens.expiresAt;\n },\n\n // Backward compatibility - computed property\n token: initialTokens?.accessToken || '',\n\n setToken: (token: string) => {\n const currentTokens = get().tokens;\n const user = get().user;\n const newTokens: TokenData = {\n accessToken: token,\n refreshToken: currentTokens?.refreshToken,\n expiresAt: currentTokens?.expiresAt,\n tokenType: currentTokens?.tokenType || 'Bearer', // Standard default\n scope: currentTokens?.scope,\n };\n \n const isAuthenticated = !!token;\n set({ tokens: newTokens, token, isAuthenticated });\n \n // Handle persistence\n if (persistence.enabled) {\n try {\n persistence.storage.setItem(persistence.tokenKey, token);\n } catch (error) {\n config.onError?.(error);\n }\n }\n },\n }));\n\n return Object.assign(store, { config });\n};\n","import { AuthConfig, validateAuthConfig } from './authConfig';\nimport { createAuthStore, AuthStore } from './authStore';\n\nexport function createAuthRegistry<AuthModels extends Record<string, any>>() {\n const registry: Record<string, AuthStore<any>> = {};\n\n function getAuthStore<K extends keyof AuthModels>(\n key: K,\n config: AuthConfig<AuthModels[K]>\n ): AuthStore<AuthModels[K]> {\n const stringKey = String(key);\n \n if (!registry[stringKey]) {\n const validatedConfig = validateAuthConfig(config);\n registry[stringKey] = createAuthStore(validatedConfig);\n }\n \n return registry[stringKey];\n }\n\n return getAuthStore;\n}","import { useEffect, useCallback } from 'react';\nimport axios from 'axios';\nimport { AuthStore } from './authStore';\nimport { TokenData } from './authConfig';\n\nexport function useAuth<U>(store: AuthStore<U>) {\n const { setTokens, setUser, unsetUser, tokens, user, isTokenExpired } = store();\n const config = store.config;\n\n // Backward compatibility\n const { setToken, token } = store();\n\n // OAuth 2.0 refresh token functionality\n const refreshTokens = useCallback(async (): Promise<boolean> => {\n if (!tokens?.refreshToken) {\n return false;\n }\n\n try {\n const response = await config.axios.post(config.tokenUrl, {\n grant_type: 'refresh_token',\n refresh_token: tokens.refreshToken,\n });\n\n const newTokens = config.extractTokens(response.data);\n setTokens(newTokens);\n setAxiosAuth(newTokens.accessToken, newTokens.tokenType);\n \n config.onTokenRefresh?.(newTokens);\n return true;\n } catch (error) {\n // Refresh failed, clear tokens\n unsetUser();\n setAxiosAuth();\n config.onError?.(error);\n return false;\n }\n }, [tokens?.refreshToken, config, setTokens, unsetUser]);\n\n // Auto-refresh and authentication setup\n useEffect(() => {\n // Handle current tokens\n if (tokens?.accessToken) {\n // Set axios headers immediately\n setAxiosAuth(tokens.accessToken, tokens.tokenType);\n\n // Check if token is expired or about to expire\n if (isTokenExpired()) {\n // Token is already expired, try to refresh\n if (tokens.refreshToken && config.autoRefresh) {\n refreshTokens();\n } else {\n unsetUser();\n }\n return;\n }\n\n // Set up auto-refresh timer if we have expiry info\n if (tokens.expiresAt && tokens.refreshToken && config.autoRefresh) {\n const timeUntilExpiry = tokens.expiresAt - Date.now();\n const refreshTime = Math.max(timeUntilExpiry - config.refreshThreshold, 0);\n\n const timer = setTimeout(() => {\n refreshTokens();\n }, refreshTime);\n\n return () => clearTimeout(timer);\n }\n\n // Try to get user info if missing\n try {\n if (!user && (config.userInfoUrl || config.getUserUrl)) {\n getCurrentUser();\n }\n } catch (error) {\n if (axios.isAxiosError(error) && error.response?.status === 403) {\n unsetUser();\n setAxiosAuth();\n }\n config.onError?.(error);\n }\n }\n }, [tokens, user, config.autoRefresh, config.refreshThreshold, config.userInfoUrl, config.getUserUrl, isTokenExpired, refreshTokens, unsetUser]);\n\n const setAxiosAuth = (token?: string, tokenType?: string) => {\n if (typeof token !== 'undefined' && token) {\n config.axios.defaults.headers.common['Authorization'] = config.formatAuthHeader(token, tokenType);\n } else {\n delete config.axios.defaults.headers.common['Authorization'];\n }\n };\n\n const login = async (\n credentials: Record<string, string>,\n callback?: () => void\n ) => {\n try {\n const res = await config.axios.post(config.tokenUrl, credentials);\n const tokens = config.extractTokens(res.data);\n setTokens(tokens);\n setAxiosAuth(tokens.accessToken, tokens.tokenType);\n \n if (config.userInfoUrl || config.getUserUrl) {\n await getCurrentUser();\n }\n \n if (user) {\n config.onLogin?.(user);\n }\n callback?.();\n } catch (err) {\n unsetUser();\n setAxiosAuth();\n config.onError?.(err);\n }\n };\n\n const getCurrentUser = async () => {\n const userUrl = config.userInfoUrl || config.getUserUrl;\n if (!userUrl) return;\n try {\n const res = await config.axios.get<U>(userUrl);\n setUser(res.data);\n } catch (err) {\n unsetUser();\n setAxiosAuth();\n config.onError?.(err);\n }\n };\n\n const logout = async () => {\n try {\n // If we have a revoke/logout URL, call it\n const logoutUrl = config.revokeUrl || config.logoutUrl;\n if (logoutUrl) {\n if (config.revokeUrl && tokens?.refreshToken) {\n // OAuth revoke\n await config.axios.post(logoutUrl, {\n token: tokens.refreshToken,\n token_type_hint: 'refresh_token'\n });\n } else {\n // Simple logout\n await config.axios.post(logoutUrl);\n }\n }\n } catch (err) {\n config.onError?.(err);\n } finally {\n unsetUser();\n setAxiosAuth();\n config.onLogout?.();\n }\n };\n\n return { login, getCurrentUser, logout, refreshTokens };\n}"],"names":["root","factory","exports","module","require","define","amd","this","__WEBPACK_EXTERNAL_MODULE__287__","__WEBPACK_EXTERNAL_MODULE__155__","__WEBPACK_EXTERNAL_MODULE__742__","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__","n","getter","__esModule","d","a","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","r","Symbol","toStringTag","value","validateAuthConfig","config","axios","Error","tokenUrl","loginUrl","revokeUrl","userInfoUrl","getUserUrl","extractTokens","data","access_token","accessToken","extractAccessToken","refreshToken","extractRefreshToken","refresh_token","expiresAt","extractExpiresIn","Date","now","expires_in","tokenType","extractTokenType","token_type","scope","extractScope","split","extractToken","auth_token","token","createTokenExtractor","defaultPersistence","enabled","storage","window","localStorage","tokenKey","refreshTokenKey","userKey","expiryKey","logoutUrl","formatAuthHeader","autoRefresh","refreshThreshold","persistence","onError","onLogin","onLogout","onTokenRefresh","createAuthStore","initialTokens","getItem","expiryString","parseInt","isNaN","getStoredTokens","initialUser","userString","JSON","parse","getStoredUser","initialIsAuthenticated","store","create","set","tokens","user","isAuthenticated","setTokens","setItem","removeItem","toString","error","setUser","stringify","unsetUser","isTokenExpired","setToken","currentTokens","newTokens","assign","createAuthRegistry","registry","stringKey","String","validatedConfig","useAuth","refreshTokens","useCallback","post","grant_type","response","setAxiosAuth","useEffect","timeUntilExpiry","refreshTime","Math","max","setTimeout","clearTimeout","getCurrentUser","status","defaults","headers","common","userUrl","res","login","credentials","callback","logout","token_type_hint"],"sourceRoot":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/react';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AuthStore } from './authStore';
|
|
2
|
+
export declare function useAuth<U>(store: AuthStore<U>): {
|
|
3
|
+
login: (credentials: Record<string, string>, callback?: () => void) => Promise<void>;
|
|
4
|
+
getCurrentUser: () => Promise<void>;
|
|
5
|
+
logout: () => Promise<void>;
|
|
6
|
+
refreshTokens: () => Promise<boolean>;
|
|
7
|
+
};
|