@jasperoosthoek/zustand-auth-registry 0.0.1 → 0.0.3
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 +107 -293
- package/dist/authConfig.d.ts +26 -20
- package/dist/authStore.d.ts +3 -3
- package/dist/errors.d.ts +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/useAuth.d.ts +3 -2
- package/package.json +2 -1
- package/src/authConfig.ts +132 -111
- package/src/authStore.ts +71 -57
- package/src/createAuthRegistry.ts +1 -1
- package/src/errors.ts +104 -0
- package/src/index.ts +2 -1
- package/src/useAuth.ts +169 -101
- package/dist/setupTests.d.ts +0 -1
- package/src/__tests__/authConfig.test.ts +0 -463
- package/src/__tests__/authStore.test.ts +0 -608
- package/src/__tests__/createAuthRegistry.test.ts +0 -202
- package/src/__tests__/testHelpers.ts +0 -92
- package/src/__tests__/testUtils.ts +0 -142
- package/src/__tests__/useAuth.test.ts +0 -975
- package/src/setupTests.ts +0 -46
package/README.md
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
# zustand-auth-registry
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
3
|
+
Authentication state management library using Zustand and Axios with a type-safe registry pattern.
|
|
8
4
|
|
|
9
5
|
## Features
|
|
10
6
|
|
|
11
|
-
- **OAuth 2.0 Compliance** - Industry-standard Bearer tokens, automatic refresh, token lifecycle management
|
|
12
7
|
- **Authentication State Management** - User, token, and authentication status with reactive updates
|
|
13
8
|
- **Registry Pattern** - Type-safe multiple auth stores per application
|
|
14
|
-
- **Axios Integration** - Automatic authentication header management
|
|
15
|
-
- **Token Lifecycle** - Automatic expiration detection
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
9
|
+
- **Axios Integration** - Automatic authentication header management
|
|
10
|
+
- **Token Lifecycle** - Automatic expiration detection and refresh workflows
|
|
11
|
+
- **Cookie Authentication** - httpOnly cookie support with CSRF protection
|
|
12
|
+
- **Typed Errors** - `AuthError` class with error codes for better error handling
|
|
13
|
+
- **Persistence** - Flexible storage options (localStorage, sessionStorage, custom)
|
|
18
14
|
- **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
15
|
|
|
23
16
|
## Installation
|
|
24
17
|
|
|
@@ -28,15 +21,13 @@ npm install @jasperoosthoek/zustand-auth-registry zustand axios react
|
|
|
28
21
|
|
|
29
22
|
## Quick Start
|
|
30
23
|
|
|
31
|
-
### OAuth 2.0 Setup (Recommended)
|
|
32
|
-
|
|
33
24
|
```typescript
|
|
34
25
|
import axios from 'axios';
|
|
35
26
|
import { createAuthRegistry, useAuth } from '@jasperoosthoek/zustand-auth-registry';
|
|
36
27
|
|
|
37
28
|
// 1. Define your user type
|
|
38
29
|
type User = {
|
|
39
|
-
id:
|
|
30
|
+
id: number;
|
|
40
31
|
email: string;
|
|
41
32
|
name: string;
|
|
42
33
|
};
|
|
@@ -49,350 +40,177 @@ const getAuthStore = createAuthRegistry<{
|
|
|
49
40
|
// 3. Create axios instance
|
|
50
41
|
const api = axios.create({ baseURL: 'https://api.example.com' });
|
|
51
42
|
|
|
52
|
-
// 4. Create
|
|
43
|
+
// 4. Create auth store
|
|
53
44
|
export const authStore = getAuthStore('main', {
|
|
54
45
|
axios: api,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
// Auto-refresh enabled by default
|
|
46
|
+
loginUrl: '/auth/login',
|
|
47
|
+
logoutUrl: '/auth/logout',
|
|
48
|
+
getUserUrl: '/users/me',
|
|
49
|
+
extractUser: 'user', // Extract user from response.data.user
|
|
60
50
|
});
|
|
61
51
|
|
|
62
52
|
// 5. Use in components
|
|
63
53
|
function LoginForm() {
|
|
64
54
|
const { login } = useAuth(authStore);
|
|
65
55
|
const { user, isAuthenticated } = authStore((s) => s);
|
|
66
|
-
|
|
56
|
+
|
|
67
57
|
const handleLogin = async () => {
|
|
68
|
-
await login({
|
|
69
|
-
username: 'user@example.com',
|
|
70
|
-
password: 'password'
|
|
71
|
-
});
|
|
58
|
+
await login({ username: 'user@example.com', password: 'password' });
|
|
72
59
|
};
|
|
73
|
-
|
|
60
|
+
|
|
74
61
|
if (isAuthenticated) {
|
|
75
62
|
return <div>Welcome {user?.name}!</div>;
|
|
76
63
|
}
|
|
77
|
-
|
|
64
|
+
|
|
78
65
|
return <button onClick={handleLogin}>Login</button>;
|
|
79
66
|
}
|
|
80
67
|
```
|
|
81
68
|
|
|
82
|
-
|
|
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
|
|
69
|
+
## Cookie Authentication
|
|
133
70
|
|
|
134
|
-
|
|
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
|
|
71
|
+
For httpOnly cookie-based authentication (more secure against XSS):
|
|
156
72
|
|
|
157
73
|
```typescript
|
|
158
74
|
const authStore = getAuthStore('main', {
|
|
159
75
|
axios: api,
|
|
160
|
-
|
|
161
|
-
|
|
76
|
+
loginUrl: '/auth/login',
|
|
77
|
+
logoutUrl: '/auth/logout',
|
|
78
|
+
authCheckUrl: '/auth/check', // Required for cookie auth
|
|
79
|
+
|
|
80
|
+
cookieAuth: {
|
|
162
81
|
enabled: true,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
82
|
+
csrf: {
|
|
83
|
+
enabled: true,
|
|
84
|
+
headerName: 'X-CSRFToken',
|
|
85
|
+
cookieName: 'csrftoken',
|
|
86
|
+
},
|
|
168
87
|
},
|
|
169
88
|
});
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### SSR/No Persistence
|
|
173
89
|
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
tokenUrl: '/oauth/token',
|
|
178
|
-
persistence: {
|
|
179
|
-
enabled: false, // Disable persistence for SSR
|
|
180
|
-
},
|
|
181
|
-
});
|
|
90
|
+
// Check authentication status (reads httpOnly cookie server-side)
|
|
91
|
+
const { checkAuth } = useAuth(authStore);
|
|
92
|
+
const isAuthenticated = await checkAuth();
|
|
182
93
|
```
|
|
183
94
|
|
|
184
|
-
##
|
|
185
|
-
|
|
186
|
-
Works seamlessly with [@jasperoosthoek/zustand-crud-registry](https://github.com/jasperoosthoek/zustand-crud-registry):
|
|
95
|
+
## Error Handling
|
|
187
96
|
|
|
188
97
|
```typescript
|
|
189
|
-
import {
|
|
190
|
-
import { createAuthRegistry } from '@jasperoosthoek/zustand-auth-registry';
|
|
191
|
-
|
|
192
|
-
// Shared axios instance
|
|
193
|
-
const api = axios.create({ baseURL: 'https://api.example.com' });
|
|
98
|
+
import { AuthError, AuthErrorCode } from '@jasperoosthoek/zustand-auth-registry';
|
|
194
99
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
100
|
+
const authStore = getAuthStore('main', {
|
|
101
|
+
// ...
|
|
102
|
+
onError: (error) => {
|
|
103
|
+
if (AuthError.isAuthError(error)) {
|
|
104
|
+
switch (error.code) {
|
|
105
|
+
case AuthErrorCode.INVALID_CREDENTIALS:
|
|
106
|
+
alert('Invalid username or password');
|
|
107
|
+
break;
|
|
108
|
+
case AuthErrorCode.TOKEN_EXPIRED:
|
|
109
|
+
// Token expired, user needs to login again
|
|
110
|
+
break;
|
|
111
|
+
case AuthErrorCode.NETWORK_ERROR:
|
|
112
|
+
alert('Network error, please try again');
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
200
117
|
});
|
|
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
118
|
```
|
|
213
119
|
|
|
214
120
|
## API Reference
|
|
215
121
|
|
|
216
|
-
### `
|
|
217
|
-
|
|
218
|
-
Creates a registry function for type-safe auth stores.
|
|
122
|
+
### `AuthConfig<U>`
|
|
219
123
|
|
|
220
124
|
```typescript
|
|
221
|
-
type
|
|
222
|
-
|
|
223
|
-
admin: AdminUser;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const getAuthStore = createAuthRegistry<Models>();
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### `getAuthStore(key, config)`
|
|
125
|
+
type AuthConfig<U> = {
|
|
126
|
+
axios: AxiosInstance;
|
|
230
127
|
|
|
231
|
-
|
|
128
|
+
// Endpoints
|
|
129
|
+
loginUrl: string;
|
|
130
|
+
logoutUrl?: string;
|
|
131
|
+
refreshUrl?: string;
|
|
132
|
+
getUserUrl?: string;
|
|
133
|
+
authCheckUrl?: string; // For cookie auth
|
|
232
134
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
- `config`: Authentication configuration
|
|
135
|
+
// Token extraction from login response
|
|
136
|
+
extractTokens?: (data: any) => TokenData;
|
|
236
137
|
|
|
237
|
-
|
|
138
|
+
// User extraction (function or string key)
|
|
139
|
+
extractUser?: ((data: any) => U | null) | string;
|
|
238
140
|
|
|
239
|
-
|
|
141
|
+
// Auth header format (default: "Bearer {token}")
|
|
142
|
+
formatAuthHeader?: (token: string, tokenType?: string) => string;
|
|
240
143
|
|
|
241
|
-
|
|
144
|
+
// Auto-refresh
|
|
145
|
+
autoRefresh?: boolean; // Default: true
|
|
146
|
+
refreshThreshold?: number; // Default: 300000 (5 minutes)
|
|
147
|
+
|
|
148
|
+
// Cookie auth
|
|
149
|
+
cookieAuth?: {
|
|
150
|
+
enabled: boolean;
|
|
151
|
+
csrf?: {
|
|
152
|
+
enabled: boolean;
|
|
153
|
+
headerName?: string; // Default: 'X-CSRFToken'
|
|
154
|
+
cookieName?: string; // Default: 'csrftoken'
|
|
155
|
+
};
|
|
156
|
+
};
|
|
242
157
|
|
|
243
|
-
|
|
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
|
|
158
|
+
// Persistence
|
|
272
159
|
persistence?: {
|
|
273
|
-
enabled
|
|
274
|
-
storage?: Storage;
|
|
275
|
-
tokenKey?: string;
|
|
276
|
-
refreshTokenKey?: string;
|
|
277
|
-
userKey?: string;
|
|
278
|
-
expiryKey?: string;
|
|
160
|
+
enabled: boolean; // Default: false
|
|
161
|
+
storage?: Storage; // Default: localStorage
|
|
162
|
+
tokenKey?: string;
|
|
163
|
+
refreshTokenKey?: string;
|
|
164
|
+
userKey?: string;
|
|
165
|
+
expiryKey?: string;
|
|
279
166
|
};
|
|
280
|
-
|
|
281
|
-
//
|
|
167
|
+
|
|
168
|
+
// Callbacks
|
|
282
169
|
onError?: (error: any) => void;
|
|
283
170
|
onLogin?: (user: U) => void;
|
|
284
171
|
onLogout?: () => void;
|
|
285
|
-
onTokenRefresh?: (tokens: TokenData) => void;
|
|
286
172
|
};
|
|
287
173
|
```
|
|
288
174
|
|
|
289
175
|
### `TokenData`
|
|
290
176
|
|
|
291
|
-
OAuth 2.0 compliant token structure.
|
|
292
|
-
|
|
293
177
|
```typescript
|
|
294
178
|
type TokenData = {
|
|
295
|
-
accessToken: string;
|
|
296
|
-
refreshToken?: string;
|
|
297
|
-
expiresAt?: number;
|
|
298
|
-
tokenType: string;
|
|
299
|
-
scope?: string[]; // OAuth scope support
|
|
179
|
+
accessToken: string;
|
|
180
|
+
refreshToken?: string;
|
|
181
|
+
expiresAt?: number;
|
|
182
|
+
tokenType: string; // Default: 'Bearer'
|
|
300
183
|
};
|
|
301
184
|
```
|
|
302
185
|
|
|
303
186
|
### `useAuth(store)`
|
|
304
187
|
|
|
305
|
-
React hook for authentication actions.
|
|
306
|
-
|
|
307
|
-
**Returns:**
|
|
308
188
|
```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
|
-
}
|
|
189
|
+
const { login, logout, getCurrentUser, refreshTokens, checkAuth } = useAuth(authStore);
|
|
315
190
|
```
|
|
316
191
|
|
|
317
|
-
|
|
192
|
+
- `login(credentials, callback?)` - Login with credentials
|
|
193
|
+
- `logout()` - Logout and clear tokens
|
|
194
|
+
- `getCurrentUser()` - Fetch current user
|
|
195
|
+
- `refreshTokens()` - Refresh access token
|
|
196
|
+
- `checkAuth()` - Check cookie authentication status
|
|
318
197
|
|
|
319
|
-
|
|
198
|
+
### Auth Store State
|
|
320
199
|
|
|
321
200
|
```typescript
|
|
322
|
-
const { user,
|
|
201
|
+
const { user, tokens, isAuthenticated, setBearerToken, setTokens } = authStore((s) => s);
|
|
323
202
|
```
|
|
324
203
|
|
|
325
204
|
**State:**
|
|
326
|
-
- `user: U | null` - Current user
|
|
327
|
-
- `
|
|
328
|
-
- `
|
|
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
|
|
205
|
+
- `user: U | null` - Current user
|
|
206
|
+
- `tokens: TokenData | null` - Token data (access token, refresh token, etc.)
|
|
207
|
+
- `isAuthenticated: boolean` - Authentication status
|
|
341
208
|
|
|
342
|
-
**
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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).
|
|
209
|
+
**Methods:**
|
|
210
|
+
- `setBearerToken(token)` - Convenience method for simple Bearer token auth
|
|
211
|
+
- `setTokens(tokenData)` - Set full token data (access, refresh, expiry)
|
|
212
|
+
- `setUser(user)` - Set user
|
|
213
|
+
- `unsetUser()` - Clear user and tokens
|
|
396
214
|
|
|
397
215
|
## Related Projects
|
|
398
216
|
|
|
@@ -401,7 +219,3 @@ This library implements OAuth 2.0 (RFC 6749) standards with Bearer Token Authent
|
|
|
401
219
|
## License
|
|
402
220
|
|
|
403
221
|
MIT
|
|
404
|
-
|
|
405
|
-
## Author
|
|
406
|
-
|
|
407
|
-
Jasper Oosthoek
|
package/dist/authConfig.d.ts
CHANGED
|
@@ -4,29 +4,30 @@ export type TokenData = {
|
|
|
4
4
|
refreshToken?: string;
|
|
5
5
|
expiresAt?: number;
|
|
6
6
|
tokenType: string;
|
|
7
|
-
scope?: string[];
|
|
8
7
|
};
|
|
9
|
-
export type TokenExtractor = (data: any) => string | TokenData;
|
|
10
8
|
export type AuthConfig<U> = {
|
|
11
9
|
axios: AxiosInstance;
|
|
12
|
-
|
|
13
|
-
revokeUrl?: string;
|
|
14
|
-
userInfoUrl?: string;
|
|
15
|
-
loginUrl?: string;
|
|
10
|
+
loginUrl: string;
|
|
16
11
|
logoutUrl?: string;
|
|
12
|
+
refreshUrl?: string;
|
|
17
13
|
getUserUrl?: string;
|
|
14
|
+
authCheckUrl?: string;
|
|
18
15
|
extractTokens?: (data: any) => TokenData;
|
|
19
|
-
|
|
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;
|
|
16
|
+
extractUser?: ((data: any) => U | null) | string;
|
|
25
17
|
formatAuthHeader?: (token: string, tokenType?: string) => string;
|
|
26
18
|
autoRefresh?: boolean;
|
|
27
19
|
refreshThreshold?: number;
|
|
20
|
+
cookieAuth?: {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
csrf?: {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
headerName?: string;
|
|
25
|
+
cookieName?: string;
|
|
26
|
+
getToken?: () => string | null;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
28
29
|
persistence?: {
|
|
29
|
-
enabled
|
|
30
|
+
enabled: boolean;
|
|
30
31
|
storage?: Storage;
|
|
31
32
|
tokenKey?: string;
|
|
32
33
|
refreshTokenKey?: string;
|
|
@@ -36,21 +37,27 @@ export type AuthConfig<U> = {
|
|
|
36
37
|
onError?: (error: any) => void;
|
|
37
38
|
onLogin?: (user: U) => void;
|
|
38
39
|
onLogout?: () => void;
|
|
39
|
-
onTokenRefresh?: (tokens: TokenData) => void;
|
|
40
40
|
};
|
|
41
41
|
export type ValidatedAuthConfig<U> = {
|
|
42
42
|
axios: AxiosInstance;
|
|
43
|
-
|
|
44
|
-
revokeUrl?: string;
|
|
45
|
-
userInfoUrl?: string;
|
|
46
|
-
loginUrl?: string;
|
|
43
|
+
loginUrl: string;
|
|
47
44
|
logoutUrl?: string;
|
|
45
|
+
refreshUrl?: string;
|
|
48
46
|
getUserUrl?: string;
|
|
47
|
+
authCheckUrl?: string;
|
|
49
48
|
extractTokens: (data: any) => TokenData;
|
|
50
|
-
|
|
49
|
+
extractUser?: (data: any) => U | null;
|
|
51
50
|
formatAuthHeader: (token: string, tokenType?: string) => string;
|
|
52
51
|
autoRefresh: boolean;
|
|
53
52
|
refreshThreshold: number;
|
|
53
|
+
cookieAuth?: {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
csrf: {
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
headerName: string;
|
|
58
|
+
getToken: () => string | null;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
54
61
|
persistence: {
|
|
55
62
|
enabled: boolean;
|
|
56
63
|
storage: Storage;
|
|
@@ -62,6 +69,5 @@ export type ValidatedAuthConfig<U> = {
|
|
|
62
69
|
onError?: (error: any) => void;
|
|
63
70
|
onLogin?: (user: U) => void;
|
|
64
71
|
onLogout?: () => void;
|
|
65
|
-
onTokenRefresh?: (tokens: TokenData) => void;
|
|
66
72
|
};
|
|
67
73
|
export declare const validateAuthConfig: <U>(config: AuthConfig<U>) => ValidatedAuthConfig<U>;
|
package/dist/authStore.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { StoreApi, UseBoundStore } from 'zustand';
|
|
2
2
|
import { ValidatedAuthConfig, TokenData } from './authConfig';
|
|
3
3
|
export type AuthState<U> = {
|
|
4
|
-
isAuthenticated: boolean;
|
|
4
|
+
isAuthenticated: boolean | null;
|
|
5
5
|
user: U | null;
|
|
6
6
|
tokens: TokenData | null;
|
|
7
7
|
setTokens: (tokens: TokenData) => void;
|
|
8
|
+
setBearerToken: (token: string) => void;
|
|
9
|
+
setAuthenticated: (authenticated: boolean) => void;
|
|
8
10
|
setUser: (user: U) => void;
|
|
9
11
|
unsetUser: () => void;
|
|
10
12
|
isTokenExpired: () => boolean;
|
|
11
|
-
token: string;
|
|
12
|
-
setToken: (token: string) => void;
|
|
13
13
|
};
|
|
14
14
|
export type AuthStore<U> = UseBoundStore<StoreApi<AuthState<U>>> & {
|
|
15
15
|
config: ValidatedAuthConfig<U>;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication error codes
|
|
3
|
+
*/
|
|
4
|
+
export declare enum AuthErrorCode {
|
|
5
|
+
INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
|
|
6
|
+
TOKEN_EXPIRED = "TOKEN_EXPIRED",
|
|
7
|
+
TOKEN_INVALID = "TOKEN_INVALID",
|
|
8
|
+
REFRESH_FAILED = "REFRESH_FAILED",
|
|
9
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
10
|
+
USER_NOT_FOUND = "USER_NOT_FOUND",
|
|
11
|
+
UNAUTHORIZED = "UNAUTHORIZED",
|
|
12
|
+
CSRF_TOKEN_MISSING = "CSRF_TOKEN_MISSING",
|
|
13
|
+
FORBIDDEN = "FORBIDDEN",
|
|
14
|
+
UNKNOWN = "UNKNOWN"
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Typed authentication error
|
|
18
|
+
*/
|
|
19
|
+
export declare class AuthError extends Error {
|
|
20
|
+
code: AuthErrorCode;
|
|
21
|
+
originalError?: any;
|
|
22
|
+
constructor(code: AuthErrorCode, originalError?: any, message?: string);
|
|
23
|
+
/**
|
|
24
|
+
* Convert error to JSON (excludes originalError in production)
|
|
25
|
+
*/
|
|
26
|
+
toJSON(): {
|
|
27
|
+
code: AuthErrorCode;
|
|
28
|
+
message: string;
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Check if error is an AuthError
|
|
33
|
+
*/
|
|
34
|
+
static isAuthError(error: any): error is AuthError;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create typed AuthError from any error
|
|
38
|
+
*/
|
|
39
|
+
export declare function createAuthError(error: any): AuthError;
|
package/dist/index.d.ts
CHANGED