astro-tokenkit 1.0.2 → 1.0.6
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/dist/auth/manager.d.ts +4 -0
- package/dist/auth/manager.js +59 -30
- package/dist/index.cjs +58 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +353 -6
- package/dist/index.js +58 -30
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import * as astro from 'astro';
|
|
2
|
+
import { AstroCookies, AstroIntegration, MiddlewareHandler } from 'astro';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Token bundle returned from auth endpoints
|
|
7
|
+
*/
|
|
8
|
+
interface TokenBundle {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
refreshToken: string;
|
|
11
|
+
accessExpiresAt: number;
|
|
12
|
+
refreshExpiresAt?: number;
|
|
13
|
+
sessionPayload?: Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Minimal context required by TokenKit
|
|
17
|
+
*/
|
|
18
|
+
interface TokenKitContext {
|
|
19
|
+
cookies: AstroCookies;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Session information
|
|
24
|
+
*/
|
|
25
|
+
interface Session {
|
|
26
|
+
accessToken: string;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
payload?: Record<string, any>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Request options
|
|
32
|
+
*/
|
|
33
|
+
interface RequestOptions {
|
|
34
|
+
/** Astro context (optional if middleware binds it) */
|
|
35
|
+
ctx?: TokenKitContext;
|
|
36
|
+
/** Additional headers */
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
/** Request timeout in ms */
|
|
39
|
+
timeout?: number;
|
|
40
|
+
/** Query parameters */
|
|
41
|
+
params?: Record<string, any>;
|
|
42
|
+
/** Skip authentication for this request */
|
|
43
|
+
skipAuth?: boolean;
|
|
44
|
+
/** Custom signal for cancellation */
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Request configuration
|
|
49
|
+
*/
|
|
50
|
+
interface RequestConfig extends RequestOptions {
|
|
51
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
52
|
+
url: string;
|
|
53
|
+
data?: any;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* HTTP response
|
|
57
|
+
*/
|
|
58
|
+
interface APIResponse<T = any> {
|
|
59
|
+
data: T;
|
|
60
|
+
status: number;
|
|
61
|
+
statusText: string;
|
|
62
|
+
headers: Headers;
|
|
63
|
+
url: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Field mapping for auto-detection
|
|
67
|
+
*/
|
|
68
|
+
interface FieldMapping {
|
|
69
|
+
accessToken?: string;
|
|
70
|
+
refreshToken?: string;
|
|
71
|
+
expiresAt?: string;
|
|
72
|
+
expiresIn?: string;
|
|
73
|
+
sessionPayload?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Auth configuration
|
|
77
|
+
*/
|
|
78
|
+
interface AuthConfig {
|
|
79
|
+
/** Login endpoint (relative to baseURL) */
|
|
80
|
+
login: string;
|
|
81
|
+
/** Refresh endpoint (relative to baseURL) */
|
|
82
|
+
refresh: string;
|
|
83
|
+
/** Logout endpoint (optional, relative to baseURL) */
|
|
84
|
+
logout?: string;
|
|
85
|
+
/** Field mapping (auto-detected if not provided) */
|
|
86
|
+
fields?: FieldMapping;
|
|
87
|
+
/** Custom login response parser */
|
|
88
|
+
parseLogin?: (body: any) => TokenBundle;
|
|
89
|
+
/** Custom refresh response parser */
|
|
90
|
+
parseRefresh?: (body: any) => TokenBundle;
|
|
91
|
+
/** Callback after successful login */
|
|
92
|
+
onLogin?: (bundle: TokenBundle, body: any, ctx: TokenKitContext) => void | Promise<void>;
|
|
93
|
+
/** Custom token injection function (default: Bearer) */
|
|
94
|
+
injectToken?: (token: string) => string;
|
|
95
|
+
/** Refresh policy */
|
|
96
|
+
policy?: RefreshPolicy;
|
|
97
|
+
/** Cookie configuration */
|
|
98
|
+
cookies?: CookieConfig;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Refresh policy
|
|
102
|
+
*/
|
|
103
|
+
interface RefreshPolicy {
|
|
104
|
+
/** Refresh before expiry (e.g., '5m' or 300) */
|
|
105
|
+
refreshBefore?: string | number;
|
|
106
|
+
/** Clock skew tolerance (e.g., '1m' or 60) */
|
|
107
|
+
clockSkew?: string | number;
|
|
108
|
+
/** Minimum interval between refreshes (e.g., '30s' or 30) */
|
|
109
|
+
minInterval?: string | number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Cookie configuration
|
|
113
|
+
*/
|
|
114
|
+
interface CookieConfig {
|
|
115
|
+
/** Secure flag (auto-detected from NODE_ENV if not set) */
|
|
116
|
+
secure?: boolean;
|
|
117
|
+
/** SameSite policy */
|
|
118
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
119
|
+
/** Cookie domain */
|
|
120
|
+
domain?: string;
|
|
121
|
+
/** Cookie names prefix */
|
|
122
|
+
prefix?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Retry configuration
|
|
126
|
+
*/
|
|
127
|
+
interface RetryConfig {
|
|
128
|
+
/** Number of retry attempts */
|
|
129
|
+
attempts?: number;
|
|
130
|
+
/** Status codes to retry */
|
|
131
|
+
statusCodes?: number[];
|
|
132
|
+
/** Backoff strategy */
|
|
133
|
+
backoff?: 'linear' | 'exponential';
|
|
134
|
+
/** Initial delay in ms */
|
|
135
|
+
delay?: number;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Request interceptor
|
|
139
|
+
*/
|
|
140
|
+
type RequestInterceptor = (config: RequestConfig, ctx?: TokenKitContext) => RequestConfig | Promise<RequestConfig>;
|
|
141
|
+
/**
|
|
142
|
+
* Response interceptor
|
|
143
|
+
*/
|
|
144
|
+
type ResponseInterceptor = <T = any>(response: APIResponse<T>, ctx?: TokenKitContext) => APIResponse<T> | Promise<APIResponse<T>>;
|
|
145
|
+
/**
|
|
146
|
+
* Error interceptor
|
|
147
|
+
*/
|
|
148
|
+
type ErrorInterceptor = (error: APIError, ctx?: TokenKitContext) => never | Promise<never>;
|
|
149
|
+
/**
|
|
150
|
+
* Interceptors configuration
|
|
151
|
+
*/
|
|
152
|
+
interface InterceptorsConfig {
|
|
153
|
+
request?: RequestInterceptor[];
|
|
154
|
+
response?: ResponseInterceptor[];
|
|
155
|
+
error?: ErrorInterceptor[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Client configuration
|
|
159
|
+
*/
|
|
160
|
+
interface ClientConfig {
|
|
161
|
+
/** Base URL for all requests */
|
|
162
|
+
baseURL: string;
|
|
163
|
+
/** Auth configuration (optional for non-auth clients) */
|
|
164
|
+
auth?: AuthConfig;
|
|
165
|
+
/** Default headers for all requests */
|
|
166
|
+
headers?: Record<string, string>;
|
|
167
|
+
/** Default timeout in ms */
|
|
168
|
+
timeout?: number;
|
|
169
|
+
/** Retry configuration */
|
|
170
|
+
retry?: RetryConfig;
|
|
171
|
+
/** Interceptors */
|
|
172
|
+
interceptors?: InterceptorsConfig;
|
|
173
|
+
/** External AsyncLocalStorage instance (optional) */
|
|
174
|
+
context?: AsyncLocalStorage<any>;
|
|
175
|
+
/** Method to get the context store (optional) */
|
|
176
|
+
getContextStore?: () => TokenKitContext | undefined | null;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* API Error
|
|
180
|
+
*/
|
|
181
|
+
declare class APIError extends Error {
|
|
182
|
+
status?: number | undefined;
|
|
183
|
+
response?: any | undefined;
|
|
184
|
+
request?: RequestConfig | undefined;
|
|
185
|
+
constructor(message: string, status?: number | undefined, response?: any | undefined, request?: RequestConfig | undefined);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Authentication Error
|
|
189
|
+
*/
|
|
190
|
+
declare class AuthError extends APIError {
|
|
191
|
+
constructor(message: string, status?: number, response?: any, request?: RequestConfig);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Network Error
|
|
195
|
+
*/
|
|
196
|
+
declare class NetworkError extends APIError {
|
|
197
|
+
constructor(message: string, request?: RequestConfig);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Timeout Error
|
|
201
|
+
*/
|
|
202
|
+
declare class TimeoutError extends APIError {
|
|
203
|
+
constructor(message: string, request?: RequestConfig);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Token Manager handles all token operations
|
|
208
|
+
*/
|
|
209
|
+
declare class TokenManager {
|
|
210
|
+
private config;
|
|
211
|
+
private singleFlight;
|
|
212
|
+
private baseURL;
|
|
213
|
+
constructor(config: AuthConfig, baseURL: string);
|
|
214
|
+
/**
|
|
215
|
+
* Perform login
|
|
216
|
+
*/
|
|
217
|
+
login(ctx: TokenKitContext, credentials: any): Promise<TokenBundle>;
|
|
218
|
+
/**
|
|
219
|
+
* Perform token refresh
|
|
220
|
+
*/
|
|
221
|
+
refresh(ctx: TokenKitContext, refreshToken: string): Promise<TokenBundle | null>;
|
|
222
|
+
/**
|
|
223
|
+
* Internal refresh implementation
|
|
224
|
+
*/
|
|
225
|
+
private performRefresh;
|
|
226
|
+
/**
|
|
227
|
+
* Ensure valid tokens (with automatic refresh)
|
|
228
|
+
*/
|
|
229
|
+
ensure(ctx: TokenKitContext): Promise<Session | null>;
|
|
230
|
+
/**
|
|
231
|
+
* Logout (clear tokens)
|
|
232
|
+
*/
|
|
233
|
+
logout(ctx: TokenKitContext): Promise<void>;
|
|
234
|
+
/**
|
|
235
|
+
* Get current session (no refresh)
|
|
236
|
+
*/
|
|
237
|
+
getSession(ctx: TokenKitContext): Session | null;
|
|
238
|
+
/**
|
|
239
|
+
* Check if authenticated
|
|
240
|
+
*/
|
|
241
|
+
isAuthenticated(ctx: TokenKitContext): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Create flight key for single-flight deduplication
|
|
244
|
+
*/
|
|
245
|
+
private createFlightKey;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Configuration for context handling
|
|
250
|
+
*/
|
|
251
|
+
interface ContextOptions {
|
|
252
|
+
context?: AsyncLocalStorage<any>;
|
|
253
|
+
getContextStore?: () => TokenKitContext | undefined | null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* API Client
|
|
258
|
+
*/
|
|
259
|
+
declare class APIClient {
|
|
260
|
+
tokenManager?: TokenManager;
|
|
261
|
+
private config;
|
|
262
|
+
contextOptions: ContextOptions;
|
|
263
|
+
constructor(config: ClientConfig);
|
|
264
|
+
/**
|
|
265
|
+
* GET request
|
|
266
|
+
*/
|
|
267
|
+
get<T = any>(url: string, options?: RequestOptions): Promise<T>;
|
|
268
|
+
/**
|
|
269
|
+
* POST request
|
|
270
|
+
*/
|
|
271
|
+
post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
|
|
272
|
+
/**
|
|
273
|
+
* PUT request
|
|
274
|
+
*/
|
|
275
|
+
put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
|
|
276
|
+
/**
|
|
277
|
+
* PATCH request
|
|
278
|
+
*/
|
|
279
|
+
patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
|
|
280
|
+
/**
|
|
281
|
+
* DELETE request
|
|
282
|
+
*/
|
|
283
|
+
delete<T = any>(url: string, options?: RequestOptions): Promise<T>;
|
|
284
|
+
/**
|
|
285
|
+
* Generic request method
|
|
286
|
+
*/
|
|
287
|
+
request<T = any>(config: RequestConfig): Promise<T>;
|
|
288
|
+
/**
|
|
289
|
+
* Execute single request
|
|
290
|
+
*/
|
|
291
|
+
private executeRequest;
|
|
292
|
+
/**
|
|
293
|
+
* Parse response
|
|
294
|
+
*/
|
|
295
|
+
private parseResponse;
|
|
296
|
+
/**
|
|
297
|
+
* Build full URL with query params
|
|
298
|
+
*/
|
|
299
|
+
private buildURL;
|
|
300
|
+
/**
|
|
301
|
+
* Build request headers
|
|
302
|
+
*/
|
|
303
|
+
private buildHeaders;
|
|
304
|
+
/**
|
|
305
|
+
* Login
|
|
306
|
+
*/
|
|
307
|
+
login(credentials: any, ctx?: TokenKitContext): Promise<void>;
|
|
308
|
+
/**
|
|
309
|
+
* Logout
|
|
310
|
+
*/
|
|
311
|
+
logout(ctx?: TokenKitContext): Promise<void>;
|
|
312
|
+
/**
|
|
313
|
+
* Check if authenticated
|
|
314
|
+
*/
|
|
315
|
+
isAuthenticated(ctx?: TokenKitContext): boolean;
|
|
316
|
+
/**
|
|
317
|
+
* Get current session
|
|
318
|
+
*/
|
|
319
|
+
getSession(ctx?: TokenKitContext): Session | null;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Create API client
|
|
323
|
+
*/
|
|
324
|
+
declare function createClient(config: ClientConfig): APIClient;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Astro integration for TokenKit
|
|
328
|
+
*
|
|
329
|
+
* This integration facilitates the setup of TokenKit in an Astro project.
|
|
330
|
+
*/
|
|
331
|
+
declare function tokenKit(client?: APIClient): AstroIntegration;
|
|
332
|
+
/**
|
|
333
|
+
* Helper to define middleware in a separate file if needed
|
|
334
|
+
*/
|
|
335
|
+
declare const defineMiddleware: (client: APIClient) => astro.MiddlewareHandler;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Create middleware for context binding and automatic token rotation
|
|
339
|
+
*/
|
|
340
|
+
declare function createMiddleware(client: APIClient): MiddlewareHandler;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parse time string to seconds
|
|
344
|
+
* Supports: '5m', '30s', '1h', '2d'
|
|
345
|
+
*/
|
|
346
|
+
declare function parseTime(input: string | number): number;
|
|
347
|
+
/**
|
|
348
|
+
* Format seconds to human-readable string
|
|
349
|
+
*/
|
|
350
|
+
declare function formatTime(seconds: number): string;
|
|
351
|
+
|
|
352
|
+
export { APIClient, APIError, AuthError, NetworkError, TimeoutError, createClient, createMiddleware, defineMiddleware, formatTime, parseTime, tokenKit };
|
|
353
|
+
export type { APIResponse, AuthConfig, ClientConfig, CookieConfig, ErrorInterceptor, FieldMapping, RefreshPolicy, RequestConfig, RequestInterceptor, RequestOptions, ResponseInterceptor, RetryConfig, Session, TokenBundle, TokenKitContext };
|
package/dist/index.js
CHANGED
|
@@ -420,17 +420,29 @@ class TokenManager {
|
|
|
420
420
|
method: 'POST',
|
|
421
421
|
headers: { 'Content-Type': 'application/json' },
|
|
422
422
|
body: JSON.stringify(credentials),
|
|
423
|
+
}).catch(error => {
|
|
424
|
+
throw new AuthError(`Login request failed: ${error.message}`);
|
|
423
425
|
});
|
|
424
426
|
if (!response.ok) {
|
|
425
|
-
throw new
|
|
427
|
+
throw new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
426
428
|
}
|
|
427
|
-
const body = yield response.json();
|
|
429
|
+
const body = yield response.json().catch(() => ({}));
|
|
428
430
|
// Parse response
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
431
|
+
let bundle;
|
|
432
|
+
try {
|
|
433
|
+
bundle = this.config.parseLogin
|
|
434
|
+
? this.config.parseLogin(body)
|
|
435
|
+
: autoDetectFields(body, this.config.fields);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
throw new AuthError(`Invalid login response: ${error.message}`, response.status, response);
|
|
439
|
+
}
|
|
432
440
|
// Store in cookies
|
|
433
441
|
storeTokens(ctx, bundle, this.config.cookies);
|
|
442
|
+
// Call onLogin callback if provided
|
|
443
|
+
if (this.config.onLogin) {
|
|
444
|
+
yield this.config.onLogin(bundle, body, ctx);
|
|
445
|
+
}
|
|
434
446
|
return bundle;
|
|
435
447
|
});
|
|
436
448
|
}
|
|
@@ -439,38 +451,54 @@ class TokenManager {
|
|
|
439
451
|
*/
|
|
440
452
|
refresh(ctx, refreshToken) {
|
|
441
453
|
return __awaiter(this, void 0, void 0, function* () {
|
|
442
|
-
const url = this.baseURL + this.config.refresh;
|
|
443
454
|
try {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
455
|
+
return yield this.performRefresh(ctx, refreshToken);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
clearTokens(ctx, this.config.cookies);
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Internal refresh implementation
|
|
465
|
+
*/
|
|
466
|
+
performRefresh(ctx, refreshToken) {
|
|
467
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
468
|
+
const url = this.baseURL + this.config.refresh;
|
|
469
|
+
const response = yield fetch(url, {
|
|
470
|
+
method: 'POST',
|
|
471
|
+
headers: { 'Content-Type': 'application/json' },
|
|
472
|
+
body: JSON.stringify({ refreshToken }),
|
|
473
|
+
}).catch(error => {
|
|
474
|
+
throw new AuthError(`Refresh request failed: ${error.message}`);
|
|
475
|
+
});
|
|
476
|
+
if (!response.ok) {
|
|
477
|
+
// 401/403 = invalid refresh token
|
|
478
|
+
if (response.status === 401 || response.status === 403) {
|
|
479
|
+
clearTokens(ctx, this.config.cookies);
|
|
480
|
+
return null;
|
|
456
481
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
482
|
+
throw new AuthError(`Refresh failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
483
|
+
}
|
|
484
|
+
const body = yield response.json().catch(() => ({}));
|
|
485
|
+
// Parse response
|
|
486
|
+
let bundle;
|
|
487
|
+
try {
|
|
488
|
+
bundle = this.config.parseRefresh
|
|
460
489
|
? this.config.parseRefresh(body)
|
|
461
490
|
: autoDetectFields(body, this.config.fields);
|
|
462
|
-
// Validate bundle
|
|
463
|
-
if (!bundle.accessToken || !bundle.refreshToken || !bundle.accessExpiresAt) {
|
|
464
|
-
throw new Error('Invalid token bundle returned from refresh endpoint');
|
|
465
|
-
}
|
|
466
|
-
// Store new tokens
|
|
467
|
-
storeTokens(ctx, bundle, this.config.cookies);
|
|
468
|
-
return bundle;
|
|
469
491
|
}
|
|
470
492
|
catch (error) {
|
|
471
|
-
|
|
472
|
-
|
|
493
|
+
throw new AuthError(`Invalid refresh response: ${error.message}`, response.status, response);
|
|
494
|
+
}
|
|
495
|
+
// Validate bundle
|
|
496
|
+
if (!bundle.accessToken || !bundle.refreshToken || !bundle.accessExpiresAt) {
|
|
497
|
+
throw new AuthError('Invalid token bundle returned from refresh endpoint', response.status, response);
|
|
473
498
|
}
|
|
499
|
+
// Store new tokens
|
|
500
|
+
storeTokens(ctx, bundle, this.config.cookies);
|
|
501
|
+
return bundle;
|
|
474
502
|
});
|
|
475
503
|
}
|
|
476
504
|
/**
|