@geekmidas/auth 0.0.1 → 0.0.2
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 +362 -0
- package/dist/__tests__/cacheTokenStorage.spec.d.cts +1 -0
- package/dist/__tests__/cacheTokenStorage.spec.d.mts +1 -0
- package/dist/__tests__/client.spec.d.cts +1 -0
- package/dist/__tests__/client.spec.d.mts +1 -0
- package/dist/__tests__/tokenManager.spec.d.cts +1 -0
- package/dist/__tests__/tokenManager.spec.d.mts +1 -0
- package/dist/cacheTokenStorage-BlpcZwef.d.mts +70 -0
- package/dist/cacheTokenStorage-yNUpDD8W.d.cts +70 -0
- package/dist/cacheTokenStorage.d.cts +2 -0
- package/dist/cacheTokenStorage.d.mts +2 -0
- package/dist/client.d.cts +2 -0
- package/dist/client.d.mts +2 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.mts +3 -0
- package/dist/playground.d.cts +1 -0
- package/dist/playground.d.mts +1 -0
- package/dist/server.d.cts +3 -0
- package/dist/server.d.mts +3 -0
- package/dist/tokenManager-CCcFk2Py.d.mts +38 -0
- package/dist/tokenManager-mpzJKLvX.d.cts +38 -0
- package/dist/tokenManager.d.cts +2 -0
- package/dist/tokenManager.d.mts +2 -0
- package/package.json +8 -8
- package/src/__tests__/cacheTokenStorage.spec.ts +24 -24
- package/src/index.ts +1 -1
- package/src/server.ts +1 -3
package/README.md
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# @geekmidas/auth
|
|
2
|
+
|
|
3
|
+
A comprehensive authentication library providing JWT token management, secure storage, and client-side token handling for TypeScript applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **JWT Token Management**: Generate, verify, and refresh JWT access and refresh tokens
|
|
8
|
+
- **Multiple Storage Options**: LocalStorage, Memory, and Cache-based token storage
|
|
9
|
+
- **Automatic Token Refresh**: Seamless token refresh with expiration handling
|
|
10
|
+
- **Type-Safe**: Full TypeScript support with complete type inference
|
|
11
|
+
- **Framework Agnostic**: Works with any JavaScript framework or vanilla JS
|
|
12
|
+
- **OpenAuth Integration**: Built on top of @openauthjs/openauth for standards compliance
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @geekmidas/auth
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Package Exports
|
|
21
|
+
|
|
22
|
+
The package provides multiple entry points for different use cases:
|
|
23
|
+
|
|
24
|
+
- `@geekmidas/auth` - Main client-side exports
|
|
25
|
+
- `@geekmidas/auth/client` - Client-side token management
|
|
26
|
+
- `@geekmidas/auth/server` - Server-side token management
|
|
27
|
+
|
|
28
|
+
## Client-Side Usage
|
|
29
|
+
|
|
30
|
+
### Basic Token Client
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { TokenClient, LocalStorageTokenStorage } from '@geekmidas/auth/client';
|
|
34
|
+
|
|
35
|
+
// Create a token client with localStorage
|
|
36
|
+
const client = new TokenClient({
|
|
37
|
+
storage: new LocalStorageTokenStorage(),
|
|
38
|
+
refreshEndpoint: '/api/auth/refresh',
|
|
39
|
+
onTokenRefresh: (tokens) => {
|
|
40
|
+
console.log('Tokens refreshed:', tokens);
|
|
41
|
+
},
|
|
42
|
+
onTokenExpired: () => {
|
|
43
|
+
console.log('Tokens expired, redirect to login');
|
|
44
|
+
window.location.href = '/login';
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Store tokens after login
|
|
49
|
+
await client.setTokens('access_token', 'refresh_token');
|
|
50
|
+
|
|
51
|
+
// Get a valid access token (automatically refreshes if expired)
|
|
52
|
+
const accessToken = await client.getValidAccessToken();
|
|
53
|
+
|
|
54
|
+
// Create authorization headers
|
|
55
|
+
const headers = await client.createValidAuthHeaders();
|
|
56
|
+
// Returns: { Authorization: 'Bearer access_token' }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Token Storage Options
|
|
60
|
+
|
|
61
|
+
#### LocalStorage (Browser)
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { LocalStorageTokenStorage } from '@geekmidas/auth/client';
|
|
65
|
+
|
|
66
|
+
const storage = new LocalStorageTokenStorage(
|
|
67
|
+
'my_access_token', // Custom access token key
|
|
68
|
+
'my_refresh_token' // Custom refresh token key
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Memory Storage
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { MemoryTokenStorage } from '@geekmidas/auth/client';
|
|
76
|
+
|
|
77
|
+
// Ideal for server-side applications or testing
|
|
78
|
+
const storage = new MemoryTokenStorage();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Cache Storage
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { CacheTokenStorage } from '@geekmidas/auth/client';
|
|
85
|
+
import { InMemoryCache } from '@geekmidas/cache/memory';
|
|
86
|
+
|
|
87
|
+
const cache = new InMemoryCache<string>();
|
|
88
|
+
const storage = new CacheTokenStorage(
|
|
89
|
+
cache,
|
|
90
|
+
'access_token', // Access token key
|
|
91
|
+
'refresh_token' // Refresh token key
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Supports TTL for automatic expiration
|
|
95
|
+
await storage.setAccessToken('token', 3600); // 1 hour TTL
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Token Validation
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// Check if a token is expired
|
|
102
|
+
const isExpired = client.isTokenExpired(token);
|
|
103
|
+
|
|
104
|
+
// Get token expiration date
|
|
105
|
+
const expiration = client.getTokenExpiration(token);
|
|
106
|
+
console.log('Token expires at:', expiration);
|
|
107
|
+
|
|
108
|
+
// Get a valid token (refreshes automatically if needed)
|
|
109
|
+
const validToken = await client.getValidAccessToken();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### HTTP Client Integration
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { TokenClient } from '@geekmidas/auth/client';
|
|
116
|
+
|
|
117
|
+
class ApiClient {
|
|
118
|
+
constructor(private tokenClient: TokenClient) {}
|
|
119
|
+
|
|
120
|
+
async makeRequest(url: string, options: RequestInit = {}) {
|
|
121
|
+
const headers = await this.tokenClient.createValidAuthHeaders();
|
|
122
|
+
|
|
123
|
+
return fetch(url, {
|
|
124
|
+
...options,
|
|
125
|
+
headers: {
|
|
126
|
+
...options.headers,
|
|
127
|
+
...headers,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const apiClient = new ApiClient(tokenClient);
|
|
134
|
+
const response = await apiClient.makeRequest('/api/users');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Server-Side Usage
|
|
138
|
+
|
|
139
|
+
### Token Manager
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { TokenManager } from '@geekmidas/auth/server';
|
|
143
|
+
|
|
144
|
+
const tokenManager = new TokenManager({
|
|
145
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
|
|
146
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
|
|
147
|
+
accessTokenExpiresIn: '15m',
|
|
148
|
+
refreshTokenExpiresIn: '7d',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Generate token pair
|
|
152
|
+
const tokens = tokenManager.generateTokenPair({
|
|
153
|
+
userId: 'user123',
|
|
154
|
+
email: 'user@example.com',
|
|
155
|
+
role: 'admin',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Verify tokens
|
|
159
|
+
try {
|
|
160
|
+
const payload = tokenManager.verifyAccessToken(accessToken);
|
|
161
|
+
console.log('User ID:', payload.userId);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('Invalid token:', error.message);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Refresh access token
|
|
167
|
+
try {
|
|
168
|
+
const newAccessToken = tokenManager.refreshAccessToken(refreshToken);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Refresh failed:', error.message);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Token Validation Middleware
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { TokenManager } from '@geekmidas/auth/server';
|
|
178
|
+
|
|
179
|
+
const tokenManager = new TokenManager({
|
|
180
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
|
|
181
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
function authenticateToken(req: Request, res: Response, next: NextFunction) {
|
|
185
|
+
const authHeader = req.headers.authorization;
|
|
186
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
187
|
+
|
|
188
|
+
if (!token) {
|
|
189
|
+
return res.status(401).json({ error: 'Access token required' });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const payload = tokenManager.verifyAccessToken(token);
|
|
194
|
+
req.user = payload;
|
|
195
|
+
next();
|
|
196
|
+
} catch (error) {
|
|
197
|
+
return res.status(403).json({ error: 'Invalid or expired token' });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## API Reference
|
|
203
|
+
|
|
204
|
+
### TokenClient
|
|
205
|
+
|
|
206
|
+
The main client for managing tokens on the client-side.
|
|
207
|
+
|
|
208
|
+
#### Constructor Options
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
interface TokenClientOptions {
|
|
212
|
+
storage?: TokenStorage; // Token storage implementation
|
|
213
|
+
refreshEndpoint?: string; // API endpoint for token refresh
|
|
214
|
+
onTokenRefresh?: (tokens: { // Callback on successful refresh
|
|
215
|
+
accessToken: string;
|
|
216
|
+
refreshToken?: string;
|
|
217
|
+
}) => void;
|
|
218
|
+
onTokenExpired?: () => void; // Callback when tokens expire
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Methods
|
|
223
|
+
|
|
224
|
+
- `getAccessToken(): Promise<string | null>` - Get stored access token
|
|
225
|
+
- `getRefreshToken(): Promise<string | null>` - Get stored refresh token
|
|
226
|
+
- `setTokens(accessToken, refreshToken?, accessTtl?, refreshTtl?): Promise<void>` - Store tokens
|
|
227
|
+
- `clearTokens(): Promise<void>` - Clear all stored tokens
|
|
228
|
+
- `isTokenExpired(token: string): boolean` - Check if token is expired
|
|
229
|
+
- `getTokenExpiration(token: string): Date | null` - Get token expiration date
|
|
230
|
+
- `refreshTokens(): Promise<boolean>` - Refresh tokens via API
|
|
231
|
+
- `getValidAccessToken(): Promise<string | null>` - Get valid token (auto-refresh)
|
|
232
|
+
- `createAuthHeaders(): Promise<Record<string, string>>` - Create auth headers
|
|
233
|
+
- `createValidAuthHeaders(): Promise<Record<string, string>>` - Create auth headers with valid token
|
|
234
|
+
|
|
235
|
+
### TokenStorage Interface
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
interface TokenStorage {
|
|
239
|
+
getAccessToken(): Promise<string | null> | string | null;
|
|
240
|
+
setAccessToken(token: string, ttl?: number): Promise<void> | void;
|
|
241
|
+
getRefreshToken(): Promise<string | null> | string | null;
|
|
242
|
+
setRefreshToken(token: string, ttl?: number): Promise<void> | void;
|
|
243
|
+
clearTokens(): Promise<void> | void;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### TokenManager
|
|
248
|
+
|
|
249
|
+
Server-side JWT token management.
|
|
250
|
+
|
|
251
|
+
#### Constructor Options
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
interface TokenManagerOptions {
|
|
255
|
+
accessTokenSecret: string; // Secret for signing access tokens
|
|
256
|
+
refreshTokenSecret: string; // Secret for signing refresh tokens
|
|
257
|
+
accessTokenExpiresIn?: string; // Access token expiration (default: '15m')
|
|
258
|
+
refreshTokenExpiresIn?: string; // Refresh token expiration (default: '7d')
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Methods
|
|
263
|
+
|
|
264
|
+
- `generateTokenPair(payload: TokenPayload): TokenPair` - Generate access and refresh tokens
|
|
265
|
+
- `verifyAccessToken(token: string): DecodedToken` - Verify and decode access token
|
|
266
|
+
- `verifyRefreshToken(token: string): DecodedToken` - Verify and decode refresh token
|
|
267
|
+
- `refreshAccessToken(refreshToken: string): string` - Generate new access token from refresh token
|
|
268
|
+
- `decodeToken(token: string): DecodedToken | null` - Decode token without verification
|
|
269
|
+
- `isTokenExpired(token: string): boolean` - Check if token is expired
|
|
270
|
+
- `getTokenExpiration(token: string): Date | null` - Get token expiration date
|
|
271
|
+
|
|
272
|
+
### Types
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
interface TokenPayload {
|
|
276
|
+
userId: string;
|
|
277
|
+
email?: string;
|
|
278
|
+
[key: string]: any;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
interface TokenPair {
|
|
282
|
+
accessToken: string;
|
|
283
|
+
refreshToken: string;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
interface DecodedToken extends TokenPayload {
|
|
287
|
+
iat: number; // Issued at
|
|
288
|
+
exp: number; // Expiration time
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Error Handling
|
|
293
|
+
|
|
294
|
+
The library provides descriptive error messages for common scenarios:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
try {
|
|
298
|
+
const payload = tokenManager.verifyAccessToken(token);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error.message.includes('Invalid access token')) {
|
|
301
|
+
// Handle invalid token
|
|
302
|
+
} else if (error.message.includes('expired')) {
|
|
303
|
+
// Handle expired token
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Security Best Practices
|
|
309
|
+
|
|
310
|
+
1. **Use Strong Secrets**: Ensure your JWT secrets are cryptographically secure and different for access and refresh tokens.
|
|
311
|
+
|
|
312
|
+
2. **Short Access Token Expiration**: Keep access tokens short-lived (15 minutes recommended).
|
|
313
|
+
|
|
314
|
+
3. **Secure Token Storage**: Use appropriate storage based on your environment:
|
|
315
|
+
- Browser: LocalStorage or secure cookies
|
|
316
|
+
- Server: Memory or secure cache
|
|
317
|
+
- Mobile: Secure keychain/keystore
|
|
318
|
+
|
|
319
|
+
4. **HTTPS Only**: Always use HTTPS in production to prevent token interception.
|
|
320
|
+
|
|
321
|
+
5. **Token Rotation**: Implement refresh token rotation for enhanced security.
|
|
322
|
+
|
|
323
|
+
## Testing
|
|
324
|
+
|
|
325
|
+
The package includes comprehensive test utilities:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { MemoryTokenStorage, TokenClient } from '@geekmidas/auth/client';
|
|
329
|
+
|
|
330
|
+
// Use memory storage for tests
|
|
331
|
+
const storage = new MemoryTokenStorage();
|
|
332
|
+
const client = new TokenClient({ storage });
|
|
333
|
+
|
|
334
|
+
// Mock refresh endpoint with MSW
|
|
335
|
+
import { http, HttpResponse } from 'msw';
|
|
336
|
+
|
|
337
|
+
const server = setupServer(
|
|
338
|
+
http.post('/auth/refresh', () => {
|
|
339
|
+
return HttpResponse.json({
|
|
340
|
+
accessToken: 'new-access-token',
|
|
341
|
+
refreshToken: 'new-refresh-token',
|
|
342
|
+
});
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Migration Guide
|
|
348
|
+
|
|
349
|
+
### From v0.0.x
|
|
350
|
+
|
|
351
|
+
The auth package is currently in initial development. Breaking changes may occur between minor versions until v1.0.0.
|
|
352
|
+
|
|
353
|
+
## Dependencies
|
|
354
|
+
|
|
355
|
+
- `@openauthjs/openauth` - OpenAuth integration
|
|
356
|
+
- `@geekmidas/cache` - Cache storage support
|
|
357
|
+
- `jsonwebtoken` - JWT token operations
|
|
358
|
+
- `@types/ms` - Time duration types
|
|
359
|
+
|
|
360
|
+
## License
|
|
361
|
+
|
|
362
|
+
MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Cache } from "@geekmidas/cache";
|
|
2
|
+
|
|
3
|
+
//#region src/client.d.ts
|
|
4
|
+
interface TokenStorage {
|
|
5
|
+
getAccessToken(): Promise<string | null> | string | null;
|
|
6
|
+
setAccessToken(token: string, ttl?: number): Promise<void> | void;
|
|
7
|
+
getRefreshToken(): Promise<string | null> | string | null;
|
|
8
|
+
setRefreshToken(token: string, ttl?: number): Promise<void> | void;
|
|
9
|
+
clearTokens(): Promise<void> | void;
|
|
10
|
+
}
|
|
11
|
+
declare class LocalStorageTokenStorage implements TokenStorage {
|
|
12
|
+
private accessTokenKey;
|
|
13
|
+
private refreshTokenKey;
|
|
14
|
+
constructor(accessTokenKey?: string, refreshTokenKey?: string);
|
|
15
|
+
getAccessToken(): string | null;
|
|
16
|
+
setAccessToken(token: string, ttl?: number): void;
|
|
17
|
+
getRefreshToken(): string | null;
|
|
18
|
+
setRefreshToken(token: string, ttl?: number): void;
|
|
19
|
+
clearTokens(): void;
|
|
20
|
+
}
|
|
21
|
+
declare class MemoryTokenStorage implements TokenStorage {
|
|
22
|
+
private accessToken;
|
|
23
|
+
private refreshToken;
|
|
24
|
+
getAccessToken(): string | null;
|
|
25
|
+
setAccessToken(token: string, ttl?: number): void;
|
|
26
|
+
getRefreshToken(): string | null;
|
|
27
|
+
setRefreshToken(token: string, ttl?: number): void;
|
|
28
|
+
clearTokens(): void;
|
|
29
|
+
}
|
|
30
|
+
interface TokenClientOptions {
|
|
31
|
+
storage?: TokenStorage;
|
|
32
|
+
refreshEndpoint?: string;
|
|
33
|
+
onTokenRefresh?: (tokens: {
|
|
34
|
+
accessToken: string;
|
|
35
|
+
refreshToken?: string;
|
|
36
|
+
}) => void;
|
|
37
|
+
onTokenExpired?: () => void;
|
|
38
|
+
}
|
|
39
|
+
declare class TokenClient {
|
|
40
|
+
private storage;
|
|
41
|
+
private refreshEndpoint?;
|
|
42
|
+
private onTokenRefresh?;
|
|
43
|
+
private onTokenExpired?;
|
|
44
|
+
constructor(options?: TokenClientOptions);
|
|
45
|
+
getAccessToken(): Promise<string | null>;
|
|
46
|
+
getRefreshToken(): Promise<string | null>;
|
|
47
|
+
setTokens(accessToken: string, refreshToken?: string, accessTtl?: number, refreshTtl?: number): Promise<void>;
|
|
48
|
+
clearTokens(): Promise<void>;
|
|
49
|
+
isTokenExpired(token: string): boolean;
|
|
50
|
+
getTokenExpiration(token: string): Date | null;
|
|
51
|
+
refreshTokens(): Promise<boolean>;
|
|
52
|
+
getValidAccessToken(): Promise<string | null>;
|
|
53
|
+
createAuthHeaders(): Promise<Record<string, string>>;
|
|
54
|
+
createValidAuthHeaders(): Promise<Record<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/cacheTokenStorage.d.ts
|
|
58
|
+
declare class CacheTokenStorage implements TokenStorage {
|
|
59
|
+
private cache;
|
|
60
|
+
private accessTokenKey;
|
|
61
|
+
private refreshTokenKey;
|
|
62
|
+
constructor(cache: Cache<string>, accessTokenKey?: string, refreshTokenKey?: string);
|
|
63
|
+
getAccessToken(): Promise<string | null>;
|
|
64
|
+
setAccessToken(token: string, ttl?: number): Promise<void>;
|
|
65
|
+
getRefreshToken(): Promise<string | null>;
|
|
66
|
+
setRefreshToken(token: string, ttl?: number): Promise<void>;
|
|
67
|
+
clearTokens(): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Cache } from "@geekmidas/cache";
|
|
2
|
+
|
|
3
|
+
//#region src/client.d.ts
|
|
4
|
+
interface TokenStorage {
|
|
5
|
+
getAccessToken(): Promise<string | null> | string | null;
|
|
6
|
+
setAccessToken(token: string, ttl?: number): Promise<void> | void;
|
|
7
|
+
getRefreshToken(): Promise<string | null> | string | null;
|
|
8
|
+
setRefreshToken(token: string, ttl?: number): Promise<void> | void;
|
|
9
|
+
clearTokens(): Promise<void> | void;
|
|
10
|
+
}
|
|
11
|
+
declare class LocalStorageTokenStorage implements TokenStorage {
|
|
12
|
+
private accessTokenKey;
|
|
13
|
+
private refreshTokenKey;
|
|
14
|
+
constructor(accessTokenKey?: string, refreshTokenKey?: string);
|
|
15
|
+
getAccessToken(): string | null;
|
|
16
|
+
setAccessToken(token: string, ttl?: number): void;
|
|
17
|
+
getRefreshToken(): string | null;
|
|
18
|
+
setRefreshToken(token: string, ttl?: number): void;
|
|
19
|
+
clearTokens(): void;
|
|
20
|
+
}
|
|
21
|
+
declare class MemoryTokenStorage implements TokenStorage {
|
|
22
|
+
private accessToken;
|
|
23
|
+
private refreshToken;
|
|
24
|
+
getAccessToken(): string | null;
|
|
25
|
+
setAccessToken(token: string, ttl?: number): void;
|
|
26
|
+
getRefreshToken(): string | null;
|
|
27
|
+
setRefreshToken(token: string, ttl?: number): void;
|
|
28
|
+
clearTokens(): void;
|
|
29
|
+
}
|
|
30
|
+
interface TokenClientOptions {
|
|
31
|
+
storage?: TokenStorage;
|
|
32
|
+
refreshEndpoint?: string;
|
|
33
|
+
onTokenRefresh?: (tokens: {
|
|
34
|
+
accessToken: string;
|
|
35
|
+
refreshToken?: string;
|
|
36
|
+
}) => void;
|
|
37
|
+
onTokenExpired?: () => void;
|
|
38
|
+
}
|
|
39
|
+
declare class TokenClient {
|
|
40
|
+
private storage;
|
|
41
|
+
private refreshEndpoint?;
|
|
42
|
+
private onTokenRefresh?;
|
|
43
|
+
private onTokenExpired?;
|
|
44
|
+
constructor(options?: TokenClientOptions);
|
|
45
|
+
getAccessToken(): Promise<string | null>;
|
|
46
|
+
getRefreshToken(): Promise<string | null>;
|
|
47
|
+
setTokens(accessToken: string, refreshToken?: string, accessTtl?: number, refreshTtl?: number): Promise<void>;
|
|
48
|
+
clearTokens(): Promise<void>;
|
|
49
|
+
isTokenExpired(token: string): boolean;
|
|
50
|
+
getTokenExpiration(token: string): Date | null;
|
|
51
|
+
refreshTokens(): Promise<boolean>;
|
|
52
|
+
getValidAccessToken(): Promise<string | null>;
|
|
53
|
+
createAuthHeaders(): Promise<Record<string, string>>;
|
|
54
|
+
createValidAuthHeaders(): Promise<Record<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/cacheTokenStorage.d.ts
|
|
58
|
+
declare class CacheTokenStorage implements TokenStorage {
|
|
59
|
+
private cache;
|
|
60
|
+
private accessTokenKey;
|
|
61
|
+
private refreshTokenKey;
|
|
62
|
+
constructor(cache: Cache<string>, accessTokenKey?: string, refreshTokenKey?: string);
|
|
63
|
+
getAccessToken(): Promise<string | null>;
|
|
64
|
+
setAccessToken(token: string, ttl?: number): Promise<void>;
|
|
65
|
+
getRefreshToken(): Promise<string | null>;
|
|
66
|
+
setRefreshToken(token: string, ttl?: number): Promise<void>;
|
|
67
|
+
clearTokens(): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage } from "./cacheTokenStorage-yNUpDD8W.cjs";
|
|
2
|
+
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage } from "./cacheTokenStorage-BlpcZwef.mjs";
|
|
2
|
+
export { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage };
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage } from "./cacheTokenStorage-yNUpDD8W.cjs";
|
|
2
|
+
import { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload } from "./tokenManager-mpzJKLvX.cjs";
|
|
3
|
+
export { CacheTokenStorage, DecodedToken, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenManager, TokenManagerOptions, TokenPair, TokenPayload, TokenStorage };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CacheTokenStorage, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenStorage } from "./cacheTokenStorage-BlpcZwef.mjs";
|
|
2
|
+
import { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload } from "./tokenManager-CCcFk2Py.mjs";
|
|
3
|
+
export { CacheTokenStorage, DecodedToken, LocalStorageTokenStorage, MemoryTokenStorage, TokenClient, TokenClientOptions, TokenManager, TokenManagerOptions, TokenPair, TokenPayload, TokenStorage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CacheTokenStorage } from "./cacheTokenStorage-yNUpDD8W.cjs";
|
|
2
|
+
import { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload } from "./tokenManager-mpzJKLvX.cjs";
|
|
3
|
+
export { CacheTokenStorage, DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CacheTokenStorage } from "./cacheTokenStorage-BlpcZwef.mjs";
|
|
2
|
+
import { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload } from "./tokenManager-CCcFk2Py.mjs";
|
|
3
|
+
export { CacheTokenStorage, DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ms from "ms";
|
|
2
|
+
|
|
3
|
+
//#region src/tokenManager.d.ts
|
|
4
|
+
interface TokenPayload {
|
|
5
|
+
userId: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
interface TokenPair {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
}
|
|
13
|
+
interface TokenManagerOptions {
|
|
14
|
+
accessTokenSecret: string;
|
|
15
|
+
refreshTokenSecret: string;
|
|
16
|
+
accessTokenExpiresIn?: ms.StringValue;
|
|
17
|
+
refreshTokenExpiresIn?: ms.StringValue;
|
|
18
|
+
}
|
|
19
|
+
interface DecodedToken extends TokenPayload {
|
|
20
|
+
iat: number;
|
|
21
|
+
exp: number;
|
|
22
|
+
}
|
|
23
|
+
declare class TokenManager {
|
|
24
|
+
private accessTokenSecret;
|
|
25
|
+
private refreshTokenSecret;
|
|
26
|
+
private accessTokenExpiresIn;
|
|
27
|
+
private refreshTokenExpiresIn;
|
|
28
|
+
constructor(options: TokenManagerOptions);
|
|
29
|
+
generateTokenPair(payload: TokenPayload): TokenPair;
|
|
30
|
+
verifyAccessToken(token: string): DecodedToken;
|
|
31
|
+
verifyRefreshToken(token: string): DecodedToken;
|
|
32
|
+
refreshAccessToken(refreshToken: string): string;
|
|
33
|
+
decodeToken(token: string): DecodedToken | null;
|
|
34
|
+
isTokenExpired(token: string): boolean;
|
|
35
|
+
getTokenExpiration(token: string): Date | null;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ms from "ms";
|
|
2
|
+
|
|
3
|
+
//#region src/tokenManager.d.ts
|
|
4
|
+
interface TokenPayload {
|
|
5
|
+
userId: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
interface TokenPair {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
}
|
|
13
|
+
interface TokenManagerOptions {
|
|
14
|
+
accessTokenSecret: string;
|
|
15
|
+
refreshTokenSecret: string;
|
|
16
|
+
accessTokenExpiresIn?: ms.StringValue;
|
|
17
|
+
refreshTokenExpiresIn?: ms.StringValue;
|
|
18
|
+
}
|
|
19
|
+
interface DecodedToken extends TokenPayload {
|
|
20
|
+
iat: number;
|
|
21
|
+
exp: number;
|
|
22
|
+
}
|
|
23
|
+
declare class TokenManager {
|
|
24
|
+
private accessTokenSecret;
|
|
25
|
+
private refreshTokenSecret;
|
|
26
|
+
private accessTokenExpiresIn;
|
|
27
|
+
private refreshTokenExpiresIn;
|
|
28
|
+
constructor(options: TokenManagerOptions);
|
|
29
|
+
generateTokenPair(payload: TokenPayload): TokenPair;
|
|
30
|
+
verifyAccessToken(token: string): DecodedToken;
|
|
31
|
+
verifyRefreshToken(token: string): DecodedToken;
|
|
32
|
+
refreshAccessToken(refreshToken: string): string;
|
|
33
|
+
decodeToken(token: string): DecodedToken | null;
|
|
34
|
+
isTokenExpired(token: string): boolean;
|
|
35
|
+
getTokenExpiration(token: string): Date | null;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DecodedToken, TokenManager, TokenManagerOptions, TokenPair, TokenPayload };
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/auth",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"import": "./dist/index.mjs",
|
|
8
|
-
"require": "./dist/index.cjs"
|
|
9
|
-
"types": "./src/index.ts"
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
10
10
|
},
|
|
11
11
|
"./server": {
|
|
12
|
+
"types": "./dist/server.d.ts",
|
|
12
13
|
"import": "./dist/server.mjs",
|
|
13
|
-
"require": "./dist/server.cjs"
|
|
14
|
-
"types": "./src/server.ts"
|
|
14
|
+
"require": "./dist/server.cjs"
|
|
15
15
|
},
|
|
16
16
|
"./client": {
|
|
17
|
+
"types": "./dist/client.d.ts",
|
|
17
18
|
"import": "./dist/client.mjs",
|
|
18
|
-
"require": "./dist/client.cjs"
|
|
19
|
-
"types": "./src/client.ts"
|
|
19
|
+
"require": "./dist/client.cjs"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@openauthjs/openauth": "~0.4.3",
|
|
28
28
|
"jsonwebtoken": "^9.0.2",
|
|
29
29
|
"@types/ms": "~2.1.0",
|
|
30
|
-
"@geekmidas/cache": "0.0.
|
|
30
|
+
"@geekmidas/cache": "0.0.4"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/jsonwebtoken": "^9.0.5"
|
|
@@ -15,32 +15,32 @@ describe('CacheTokenStorage', () => {
|
|
|
15
15
|
describe('access token operations', () => {
|
|
16
16
|
it('should store and retrieve access token', async () => {
|
|
17
17
|
await storage.setAccessToken('test-access-token');
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
const token = await storage.getAccessToken();
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
expect(token).toBe('test-access-token');
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('should return null when no access token exists', async () => {
|
|
25
25
|
const token = await storage.getAccessToken();
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
expect(token).toBeNull();
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('should store access token with TTL', async () => {
|
|
31
31
|
await storage.setAccessToken('test-access-token', 3600);
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
const token = await storage.getAccessToken();
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
expect(token).toBe('test-access-token');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it('should overwrite existing access token', async () => {
|
|
39
39
|
await storage.setAccessToken('first-token');
|
|
40
40
|
await storage.setAccessToken('second-token');
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
const token = await storage.getAccessToken();
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
expect(token).toBe('second-token');
|
|
45
45
|
});
|
|
46
46
|
});
|
|
@@ -48,32 +48,32 @@ describe('CacheTokenStorage', () => {
|
|
|
48
48
|
describe('refresh token operations', () => {
|
|
49
49
|
it('should store and retrieve refresh token', async () => {
|
|
50
50
|
await storage.setRefreshToken('test-refresh-token');
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
const token = await storage.getRefreshToken();
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
expect(token).toBe('test-refresh-token');
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('should return null when no refresh token exists', async () => {
|
|
58
58
|
const token = await storage.getRefreshToken();
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
expect(token).toBeNull();
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('should store refresh token with TTL', async () => {
|
|
64
64
|
await storage.setRefreshToken('test-refresh-token', 7200);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
const token = await storage.getRefreshToken();
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
expect(token).toBe('test-refresh-token');
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it('should overwrite existing refresh token', async () => {
|
|
72
72
|
await storage.setRefreshToken('first-refresh-token');
|
|
73
73
|
await storage.setRefreshToken('second-refresh-token');
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
const token = await storage.getRefreshToken();
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
expect(token).toBe('second-refresh-token');
|
|
78
78
|
});
|
|
79
79
|
});
|
|
@@ -82,10 +82,10 @@ describe('CacheTokenStorage', () => {
|
|
|
82
82
|
it('should store both tokens independently', async () => {
|
|
83
83
|
await storage.setAccessToken('access-token');
|
|
84
84
|
await storage.setRefreshToken('refresh-token');
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
const accessToken = await storage.getAccessToken();
|
|
87
87
|
const refreshToken = await storage.getRefreshToken();
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
expect(accessToken).toBe('access-token');
|
|
90
90
|
expect(refreshToken).toBe('refresh-token');
|
|
91
91
|
});
|
|
@@ -93,24 +93,24 @@ describe('CacheTokenStorage', () => {
|
|
|
93
93
|
it('should clear both tokens', async () => {
|
|
94
94
|
await storage.setAccessToken('access-token');
|
|
95
95
|
await storage.setRefreshToken('refresh-token');
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
await storage.clearTokens();
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
const accessToken = await storage.getAccessToken();
|
|
100
100
|
const refreshToken = await storage.getRefreshToken();
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
expect(accessToken).toBeNull();
|
|
103
103
|
expect(refreshToken).toBeNull();
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
it('should clear tokens when some are missing', async () => {
|
|
107
107
|
await storage.setAccessToken('access-token');
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
await storage.clearTokens();
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
const accessToken = await storage.getAccessToken();
|
|
112
112
|
const refreshToken = await storage.getRefreshToken();
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
expect(accessToken).toBeNull();
|
|
115
115
|
expect(refreshToken).toBeNull();
|
|
116
116
|
});
|
|
@@ -177,7 +177,7 @@ describe('CacheTokenStorage', () => {
|
|
|
177
177
|
it('should work with multiple cache instances', async () => {
|
|
178
178
|
const cache1 = new InMemoryCache<string>();
|
|
179
179
|
const cache2 = new InMemoryCache<string>();
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
const storage1 = new CacheTokenStorage(cache1);
|
|
182
182
|
const storage2 = new CacheTokenStorage(cache2);
|
|
183
183
|
|
|
@@ -210,4 +210,4 @@ describe('CacheTokenStorage', () => {
|
|
|
210
210
|
expect(refreshToken).toBe('initial-refresh');
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
|
-
});
|
|
213
|
+
});
|
package/src/index.ts
CHANGED