astro-tokenkit 1.0.9 → 1.0.11
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 +24 -3
- package/dist/auth/manager.d.ts +4 -4
- package/dist/auth/manager.js +34 -13
- package/dist/client/client.d.ts +5 -5
- package/dist/client/client.js +11 -21
- package/dist/client/context-shared.d.ts +1 -1
- package/dist/client/context-shared.js +2 -8
- package/dist/client/context.d.ts +2 -2
- package/dist/client/context.js +6 -9
- package/dist/config.js +8 -2
- package/dist/index.cjs +68 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +41 -13
- package/dist/index.js +67 -52
- package/dist/index.js.map +1 -1
- package/dist/middleware.js +9 -8
- package/dist/types.d.ts +24 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,11 @@ const specializedClient = createClient({
|
|
|
125
125
|
| `login` | `string` | Endpoint path for login (POST). |
|
|
126
126
|
| `refresh` | `string` | Endpoint path for token refresh (POST). |
|
|
127
127
|
| `logout` | `string` | Endpoint path for logout (POST). |
|
|
128
|
+
| `contentType` | `'application/json' \| 'application/x-www-form-urlencoded'` | Content type for auth requests (default: `application/json`). |
|
|
129
|
+
| `headers` | `Record<string, string>` | Extra headers for login/refresh requests. |
|
|
130
|
+
| `loginData` | `Record<string, any>` | Extra data to be sent with login request. |
|
|
131
|
+
| `refreshData` | `Record<string, any>` | Extra data to be sent with refresh request. |
|
|
132
|
+
| `refreshRequestField` | `string` | Field name for the refresh token in the refresh request (default: `refreshToken`). |
|
|
128
133
|
| `fields` | `FieldMapping` | Custom mapping for token fields in API responses. |
|
|
129
134
|
| `parseLogin` | `Function` | Custom parser for login response: `(body: any) => TokenBundle`. |
|
|
130
135
|
| `parseRefresh`| `Function` | Custom parser for refresh response: `(body: any) => TokenBundle`. |
|
|
@@ -136,17 +141,33 @@ const specializedClient = createClient({
|
|
|
136
141
|
|
|
137
142
|
| Property | Type | Description |
|
|
138
143
|
| :--- | :--- | :--- |
|
|
139
|
-
| `ctx` | `TokenKitContext` | Optional Astro context. |
|
|
140
144
|
| `onLogin` | `Function` | Callback after successful login: `(bundle, body, ctx) => void`. |
|
|
145
|
+
| `headers` | `Record<string, string>` | Extra headers for this specific login request. |
|
|
146
|
+
| `data` | `Record<string, any>` | Extra data for this specific login request. |
|
|
147
|
+
|
|
148
|
+
### Request Auth Overrides
|
|
149
|
+
|
|
150
|
+
When calling `api.get()`, `api.post()`, etc., you can override auth configuration (e.g., for multi-tenancy). Headers provided in the request options are automatically propagated to any automatic token refresh operations:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
await api.get('/data', {
|
|
154
|
+
headers: { 'x-tenant-name': 'lynx' },
|
|
155
|
+
auth: {
|
|
156
|
+
data: { extra_refresh_param: 'value' }
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
141
160
|
|
|
142
161
|
## Advanced Usage
|
|
143
162
|
|
|
144
163
|
### Manual Context
|
|
145
164
|
|
|
146
|
-
If you prefer not to use middleware, you can
|
|
165
|
+
If you prefer not to use middleware, you can bind the Astro context manually for a specific scope:
|
|
147
166
|
|
|
148
167
|
```typescript
|
|
149
|
-
|
|
168
|
+
import { runWithContext } from 'astro-tokenkit';
|
|
169
|
+
|
|
170
|
+
const data = await runWithContext(Astro, () => api.get('/data'));
|
|
150
171
|
```
|
|
151
172
|
|
|
152
173
|
### Interceptors
|
package/dist/auth/manager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TokenBundle, Session, AuthConfig, TokenKitContext,
|
|
1
|
+
import type { TokenBundle, Session, AuthConfig, TokenKitContext, AuthOptions, LoginOptions } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Token Manager handles all token operations
|
|
4
4
|
*/
|
|
@@ -10,11 +10,11 @@ export declare class TokenManager {
|
|
|
10
10
|
/**
|
|
11
11
|
* Perform login
|
|
12
12
|
*/
|
|
13
|
-
login(ctx: TokenKitContext, credentials: any,
|
|
13
|
+
login(ctx: TokenKitContext, credentials: any, options?: LoginOptions): Promise<TokenBundle>;
|
|
14
14
|
/**
|
|
15
15
|
* Perform token refresh
|
|
16
16
|
*/
|
|
17
|
-
refresh(ctx: TokenKitContext, refreshToken: string): Promise<TokenBundle | null>;
|
|
17
|
+
refresh(ctx: TokenKitContext, refreshToken: string, options?: AuthOptions, headers?: Record<string, string>): Promise<TokenBundle | null>;
|
|
18
18
|
/**
|
|
19
19
|
* Internal refresh implementation
|
|
20
20
|
*/
|
|
@@ -22,7 +22,7 @@ export declare class TokenManager {
|
|
|
22
22
|
/**
|
|
23
23
|
* Ensure valid tokens (with automatic refresh)
|
|
24
24
|
*/
|
|
25
|
-
ensure(ctx: TokenKitContext): Promise<Session | null>;
|
|
25
|
+
ensure(ctx: TokenKitContext, options?: AuthOptions, headers?: Record<string, string>): Promise<Session | null>;
|
|
26
26
|
/**
|
|
27
27
|
* Logout (clear tokens)
|
|
28
28
|
*/
|
package/dist/auth/manager.js
CHANGED
|
@@ -52,13 +52,23 @@ export class TokenManager {
|
|
|
52
52
|
/**
|
|
53
53
|
* Perform login
|
|
54
54
|
*/
|
|
55
|
-
login(ctx, credentials,
|
|
55
|
+
login(ctx, credentials, options) {
|
|
56
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
57
57
|
const url = this.baseURL + this.config.login;
|
|
58
|
+
const contentType = this.config.contentType || 'application/json';
|
|
59
|
+
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
60
|
+
const data = Object.assign(Object.assign(Object.assign({}, this.config.loginData), options === null || options === void 0 ? void 0 : options.data), credentials);
|
|
61
|
+
let requestBody;
|
|
62
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
63
|
+
requestBody = new URLSearchParams(data).toString();
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
requestBody = JSON.stringify(data);
|
|
67
|
+
}
|
|
58
68
|
const response = yield fetch(url, {
|
|
59
69
|
method: 'POST',
|
|
60
|
-
headers
|
|
61
|
-
body:
|
|
70
|
+
headers,
|
|
71
|
+
body: requestBody,
|
|
62
72
|
}).catch(error => {
|
|
63
73
|
throw new AuthError(`Login request failed: ${error.message}`);
|
|
64
74
|
});
|
|
@@ -79,8 +89,8 @@ export class TokenManager {
|
|
|
79
89
|
// Store in cookies
|
|
80
90
|
storeTokens(ctx, bundle, this.config.cookies);
|
|
81
91
|
// Call onLogin callback if provided
|
|
82
|
-
if (onLogin) {
|
|
83
|
-
yield onLogin(bundle, body, ctx);
|
|
92
|
+
if (options === null || options === void 0 ? void 0 : options.onLogin) {
|
|
93
|
+
yield options.onLogin(bundle, body, ctx);
|
|
84
94
|
}
|
|
85
95
|
return bundle;
|
|
86
96
|
});
|
|
@@ -88,10 +98,10 @@ export class TokenManager {
|
|
|
88
98
|
/**
|
|
89
99
|
* Perform token refresh
|
|
90
100
|
*/
|
|
91
|
-
refresh(ctx, refreshToken) {
|
|
101
|
+
refresh(ctx, refreshToken, options, headers) {
|
|
92
102
|
return __awaiter(this, void 0, void 0, function* () {
|
|
93
103
|
try {
|
|
94
|
-
return yield this.performRefresh(ctx, refreshToken);
|
|
104
|
+
return yield this.performRefresh(ctx, refreshToken, options, headers);
|
|
95
105
|
}
|
|
96
106
|
catch (error) {
|
|
97
107
|
clearTokens(ctx, this.config.cookies);
|
|
@@ -102,13 +112,24 @@ export class TokenManager {
|
|
|
102
112
|
/**
|
|
103
113
|
* Internal refresh implementation
|
|
104
114
|
*/
|
|
105
|
-
performRefresh(ctx, refreshToken) {
|
|
115
|
+
performRefresh(ctx, refreshToken, options, extraHeaders) {
|
|
106
116
|
return __awaiter(this, void 0, void 0, function* () {
|
|
107
117
|
const url = this.baseURL + this.config.refresh;
|
|
118
|
+
const contentType = this.config.contentType || 'application/json';
|
|
119
|
+
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
|
|
120
|
+
const refreshField = this.config.refreshRequestField || 'refreshToken';
|
|
121
|
+
const data = Object.assign(Object.assign(Object.assign({}, this.config.refreshData), options === null || options === void 0 ? void 0 : options.data), { [refreshField]: refreshToken });
|
|
122
|
+
let requestBody;
|
|
123
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
|
124
|
+
requestBody = new URLSearchParams(data).toString();
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
requestBody = JSON.stringify(data);
|
|
128
|
+
}
|
|
108
129
|
const response = yield fetch(url, {
|
|
109
130
|
method: 'POST',
|
|
110
|
-
headers
|
|
111
|
-
body:
|
|
131
|
+
headers,
|
|
132
|
+
body: requestBody,
|
|
112
133
|
}).catch(error => {
|
|
113
134
|
throw new AuthError(`Refresh request failed: ${error.message}`);
|
|
114
135
|
});
|
|
@@ -143,7 +164,7 @@ export class TokenManager {
|
|
|
143
164
|
/**
|
|
144
165
|
* Ensure valid tokens (with automatic refresh)
|
|
145
166
|
*/
|
|
146
|
-
ensure(ctx) {
|
|
167
|
+
ensure(ctx, options, headers) {
|
|
147
168
|
return __awaiter(this, void 0, void 0, function* () {
|
|
148
169
|
var _a, _b, _c, _d, _e;
|
|
149
170
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -155,7 +176,7 @@ export class TokenManager {
|
|
|
155
176
|
// Token expired
|
|
156
177
|
if (isExpired(tokens.expiresAt, now, this.config.policy)) {
|
|
157
178
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
158
|
-
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
|
|
179
|
+
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
159
180
|
if (!bundle)
|
|
160
181
|
return null;
|
|
161
182
|
return {
|
|
@@ -167,7 +188,7 @@ export class TokenManager {
|
|
|
167
188
|
// Proactive refresh
|
|
168
189
|
if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
|
|
169
190
|
const flightKey = this.createFlightKey(tokens.refreshToken);
|
|
170
|
-
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
|
|
191
|
+
const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken, options, headers));
|
|
171
192
|
if (bundle) {
|
|
172
193
|
return {
|
|
173
194
|
accessToken: bundle.accessToken,
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientConfig, RequestConfig, RequestOptions, Session,
|
|
1
|
+
import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions } from '../types';
|
|
2
2
|
import { TokenManager } from '../auth/manager';
|
|
3
3
|
/**
|
|
4
4
|
* API Client
|
|
@@ -65,19 +65,19 @@ export declare class APIClient {
|
|
|
65
65
|
/**
|
|
66
66
|
* Login
|
|
67
67
|
*/
|
|
68
|
-
login(credentials: any, options?: LoginOptions
|
|
68
|
+
login(credentials: any, options?: LoginOptions): Promise<void>;
|
|
69
69
|
/**
|
|
70
70
|
* Logout
|
|
71
71
|
*/
|
|
72
|
-
logout(
|
|
72
|
+
logout(): Promise<void>;
|
|
73
73
|
/**
|
|
74
74
|
* Check if authenticated
|
|
75
75
|
*/
|
|
76
|
-
isAuthenticated(
|
|
76
|
+
isAuthenticated(): boolean;
|
|
77
77
|
/**
|
|
78
78
|
* Get current session
|
|
79
79
|
*/
|
|
80
|
-
getSession(
|
|
80
|
+
getSession(): Session | null;
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Global API client instance.
|
package/dist/client/client.js
CHANGED
|
@@ -113,7 +113,7 @@ export class APIClient {
|
|
|
113
113
|
*/
|
|
114
114
|
request(config) {
|
|
115
115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
-
const ctx = getContextStore(
|
|
116
|
+
const ctx = getContextStore();
|
|
117
117
|
let attempt = 0;
|
|
118
118
|
let lastError;
|
|
119
119
|
while (true) {
|
|
@@ -144,7 +144,7 @@ export class APIClient {
|
|
|
144
144
|
var _a, _b, _c, _d, _e;
|
|
145
145
|
// Ensure valid session (if auth is enabled)
|
|
146
146
|
if (this.tokenManager && !config.skipAuth) {
|
|
147
|
-
yield this.tokenManager.ensure(ctx);
|
|
147
|
+
yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
148
148
|
}
|
|
149
149
|
// Build full URL
|
|
150
150
|
const fullURL = this.buildURL(config.url, config.params);
|
|
@@ -177,7 +177,7 @@ export class APIClient {
|
|
|
177
177
|
// Handle 401 (try refresh and retry once)
|
|
178
178
|
if (response.status === 401 && this.tokenManager && !config.skipAuth && attempt === 1) {
|
|
179
179
|
// Clear and try fresh session
|
|
180
|
-
const session = yield this.tokenManager.ensure(ctx);
|
|
180
|
+
const session = yield this.tokenManager.ensure(ctx, config.auth, config.headers);
|
|
181
181
|
if (session) {
|
|
182
182
|
// Retry with new token
|
|
183
183
|
return this.executeRequest(config, ctx, attempt + 1);
|
|
@@ -288,48 +288,38 @@ export class APIClient {
|
|
|
288
288
|
if (!this.tokenManager) {
|
|
289
289
|
throw new Error('Auth is not configured for this client');
|
|
290
290
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (options && 'cookies' in options) {
|
|
294
|
-
ctx = options;
|
|
295
|
-
}
|
|
296
|
-
else if (options) {
|
|
297
|
-
const opt = options;
|
|
298
|
-
ctx = opt.ctx;
|
|
299
|
-
onLogin = opt.onLogin;
|
|
300
|
-
}
|
|
301
|
-
const context = getContextStore(ctx);
|
|
302
|
-
yield this.tokenManager.login(context, credentials, onLogin);
|
|
291
|
+
const context = getContextStore();
|
|
292
|
+
yield this.tokenManager.login(context, credentials, options);
|
|
303
293
|
});
|
|
304
294
|
}
|
|
305
295
|
/**
|
|
306
296
|
* Logout
|
|
307
297
|
*/
|
|
308
|
-
logout(
|
|
298
|
+
logout() {
|
|
309
299
|
return __awaiter(this, void 0, void 0, function* () {
|
|
310
300
|
if (!this.tokenManager) {
|
|
311
301
|
throw new Error('Auth is not configured for this client');
|
|
312
302
|
}
|
|
313
|
-
const context = getContextStore(
|
|
303
|
+
const context = getContextStore();
|
|
314
304
|
yield this.tokenManager.logout(context);
|
|
315
305
|
});
|
|
316
306
|
}
|
|
317
307
|
/**
|
|
318
308
|
* Check if authenticated
|
|
319
309
|
*/
|
|
320
|
-
isAuthenticated(
|
|
310
|
+
isAuthenticated() {
|
|
321
311
|
if (!this.tokenManager)
|
|
322
312
|
return false;
|
|
323
|
-
const context = getContextStore(
|
|
313
|
+
const context = getContextStore();
|
|
324
314
|
return this.tokenManager.isAuthenticated(context);
|
|
325
315
|
}
|
|
326
316
|
/**
|
|
327
317
|
* Get current session
|
|
328
318
|
*/
|
|
329
|
-
getSession(
|
|
319
|
+
getSession() {
|
|
330
320
|
if (!this.tokenManager)
|
|
331
321
|
return null;
|
|
332
|
-
const context = getContextStore(
|
|
322
|
+
const context = getContextStore();
|
|
333
323
|
return this.tokenManager.getSession(context);
|
|
334
324
|
}
|
|
335
325
|
}
|
|
@@ -10,7 +10,7 @@ export declare function setSharedContextStorage(storage: AsyncLocalStorage<any>,
|
|
|
10
10
|
/**
|
|
11
11
|
* Get context from shared storage
|
|
12
12
|
*/
|
|
13
|
-
export declare function getContextStore(
|
|
13
|
+
export declare function getContextStore(): TokenKitContext;
|
|
14
14
|
/**
|
|
15
15
|
* Bind context (only needed if not using shared storage)
|
|
16
16
|
*/
|
|
@@ -33,10 +33,7 @@ export function setSharedContextStorage(storage, key = 'astro') {
|
|
|
33
33
|
/**
|
|
34
34
|
* Get context from shared storage
|
|
35
35
|
*/
|
|
36
|
-
export function getContextStore(
|
|
37
|
-
if (explicitCtx) {
|
|
38
|
-
return explicitCtx;
|
|
39
|
-
}
|
|
36
|
+
export function getContextStore() {
|
|
40
37
|
const config = getConfig();
|
|
41
38
|
const getStore = config.getContextStore;
|
|
42
39
|
if (getStore) {
|
|
@@ -51,10 +48,7 @@ export function getContextStore(explicitCtx) {
|
|
|
51
48
|
return ctx;
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
throw new Error('Astro context not found.
|
|
55
|
-
'1. Use api.middleware() to bind context automatically, or\n' +
|
|
56
|
-
'2. Pass context explicitly: api.get("/path", { ctx: Astro })\n' +
|
|
57
|
-
'3. Configure shared storage: setSharedContextStorage(storage, "key")');
|
|
51
|
+
throw new Error('Astro context not found. Make sure to use api.middleware() to bind context automatically.');
|
|
58
52
|
}
|
|
59
53
|
/**
|
|
60
54
|
* Bind context (only needed if not using shared storage)
|
package/dist/client/context.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ export declare function runWithContext<T>(ctx: TokenKitContext, fn: () => T): T;
|
|
|
6
6
|
/**
|
|
7
7
|
* Get current Astro context (from middleware binding or explicit)
|
|
8
8
|
*/
|
|
9
|
-
export declare function getContextStore(
|
|
9
|
+
export declare function getContextStore(): TokenKitContext;
|
|
10
10
|
/**
|
|
11
11
|
* Check if context is available
|
|
12
12
|
*/
|
|
13
|
-
export declare function hasContext(
|
|
13
|
+
export declare function hasContext(): boolean;
|
package/dist/client/context.js
CHANGED
|
@@ -19,30 +19,27 @@ export function runWithContext(ctx, fn) {
|
|
|
19
19
|
/**
|
|
20
20
|
* Get current Astro context (from middleware binding or explicit)
|
|
21
21
|
*/
|
|
22
|
-
export function getContextStore(
|
|
22
|
+
export function getContextStore() {
|
|
23
23
|
const config = getConfig();
|
|
24
24
|
const getStore = config.getContextStore;
|
|
25
25
|
const context = config.context || als;
|
|
26
26
|
const store = getStore
|
|
27
27
|
? getStore()
|
|
28
28
|
: context.getStore();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
throw new Error('Astro context not found. Either:\n' +
|
|
32
|
-
'1. Use api.middleware() to bind context automatically, or\n' +
|
|
33
|
-
'2. Pass context explicitly: api.get("/path", { ctx: Astro })');
|
|
29
|
+
if (!store) {
|
|
30
|
+
throw new Error('Astro context not found. Make sure to use api.middleware() to bind context automatically.');
|
|
34
31
|
}
|
|
35
|
-
return
|
|
32
|
+
return store;
|
|
36
33
|
}
|
|
37
34
|
/**
|
|
38
35
|
* Check if context is available
|
|
39
36
|
*/
|
|
40
|
-
export function hasContext(
|
|
37
|
+
export function hasContext() {
|
|
41
38
|
const config = getConfig();
|
|
42
39
|
const getStore = config.getContextStore;
|
|
43
40
|
const context = config.context || als;
|
|
44
41
|
const store = getStore
|
|
45
42
|
? getStore()
|
|
46
43
|
: context.getStore();
|
|
47
|
-
return !!
|
|
44
|
+
return !!store;
|
|
48
45
|
}
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { TokenManager } from "./auth/manager";
|
|
|
3
3
|
let config = {
|
|
4
4
|
runWithContext: undefined,
|
|
5
5
|
getContextStore: undefined,
|
|
6
|
+
setContextStore: undefined,
|
|
6
7
|
baseURL: "",
|
|
7
8
|
};
|
|
8
9
|
let tokenManager;
|
|
@@ -10,8 +11,13 @@ let tokenManager;
|
|
|
10
11
|
* Set configuration
|
|
11
12
|
*/
|
|
12
13
|
export function setConfig(userConfig) {
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const finalConfig = Object.assign(Object.assign({}, config), userConfig);
|
|
15
|
+
// Validate that getter and setter are defined together
|
|
16
|
+
if ((finalConfig.getContextStore && !finalConfig.setContextStore) ||
|
|
17
|
+
(!finalConfig.getContextStore && finalConfig.setContextStore)) {
|
|
18
|
+
throw new Error("[TokenKit] getContextStore and setContextStore must be defined together.");
|
|
19
|
+
}
|
|
20
|
+
config = finalConfig;
|
|
15
21
|
// Re-initialize global token manager if auth changed
|
|
16
22
|
if (config.auth) {
|
|
17
23
|
tokenManager = new TokenManager(config.auth, config.baseURL);
|