astro-tokenkit 1.0.11 → 1.0.13
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 +47 -6
- package/dist/auth/detector.js +14 -0
- package/dist/auth/manager.d.ts +6 -1
- package/dist/auth/manager.js +70 -32
- package/dist/auth/policy.js +2 -1
- package/dist/auth/storage.d.ts +2 -0
- package/dist/auth/storage.js +11 -4
- package/dist/client/client.d.ts +12 -8
- package/dist/client/client.js +23 -11
- package/dist/config.js +34 -14
- package/dist/index.cjs +170 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +28 -10
- package/dist/index.js +170 -64
- package/dist/index.js.map +1 -1
- package/dist/integration.js +14 -2
- package/dist/middleware.js +2 -2
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,8 +61,8 @@ Now you can use the `api` client anywhere in your Astro pages or components with
|
|
|
61
61
|
// src/pages/profile.astro
|
|
62
62
|
import { api } from 'astro-tokenkit';
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
const user = await api.get('/me');
|
|
64
|
+
// Request methods return an APIResponse object
|
|
65
|
+
const { data: user } = await api.get('/me');
|
|
66
66
|
---
|
|
67
67
|
|
|
68
68
|
<h1>Welcome, {user.name}</h1>
|
|
@@ -130,10 +130,10 @@ const specializedClient = createClient({
|
|
|
130
130
|
| `loginData` | `Record<string, any>` | Extra data to be sent with login request. |
|
|
131
131
|
| `refreshData` | `Record<string, any>` | Extra data to be sent with refresh request. |
|
|
132
132
|
| `refreshRequestField` | `string` | Field name for the refresh token in the refresh request (default: `refreshToken`). |
|
|
133
|
-
| `fields` | `FieldMapping` | Custom mapping for token fields in API responses. |
|
|
133
|
+
| `fields` | `FieldMapping` | Custom mapping for token fields in API responses (`accessToken`, `refreshToken`, `expiresAt`, `expiresIn`, `tokenType`, `sessionPayload`). |
|
|
134
134
|
| `parseLogin` | `Function` | Custom parser for login response: `(body: any) => TokenBundle`. |
|
|
135
135
|
| `parseRefresh`| `Function` | Custom parser for refresh response: `(body: any) => TokenBundle`. |
|
|
136
|
-
| `injectToken` | `Function` | Custom token injection: `(token: string) => string` (default: Bearer). |
|
|
136
|
+
| `injectToken` | `Function` | Custom token injection: `(token: string, type?: string) => string` (default: Bearer). |
|
|
137
137
|
| `cookies` | `CookieConfig` | Configuration for auth cookies. |
|
|
138
138
|
| `policy` | `RefreshPolicy` | Strategy for when to trigger token refresh. |
|
|
139
139
|
|
|
@@ -142,6 +142,7 @@ const specializedClient = createClient({
|
|
|
142
142
|
| Property | Type | Description |
|
|
143
143
|
| :--- | :--- | :--- |
|
|
144
144
|
| `onLogin` | `Function` | Callback after successful login: `(bundle, body, ctx) => void`. |
|
|
145
|
+
| `onError` | `Function` | Callback after failed login: `(error, ctx) => void`. |
|
|
145
146
|
| `headers` | `Record<string, string>` | Extra headers for this specific login request. |
|
|
146
147
|
| `data` | `Record<string, any>` | Extra data for this specific login request. |
|
|
147
148
|
|
|
@@ -167,7 +168,7 @@ If you prefer not to use middleware, you can bind the Astro context manually for
|
|
|
167
168
|
```typescript
|
|
168
169
|
import { runWithContext } from 'astro-tokenkit';
|
|
169
170
|
|
|
170
|
-
const data = await runWithContext(Astro, () => api.get('/data'));
|
|
171
|
+
const { data } = await runWithContext(Astro, () => api.get('/data'));
|
|
171
172
|
```
|
|
172
173
|
|
|
173
174
|
### Interceptors
|
|
@@ -190,16 +191,56 @@ const api = createClient({
|
|
|
190
191
|
|
|
191
192
|
```typescript
|
|
192
193
|
// In an API route or server-side component
|
|
193
|
-
await api.login({ username, password }, {
|
|
194
|
+
const { data: bundle } = await api.login({ username, password }, {
|
|
194
195
|
onLogin: (bundle, body, ctx) => {
|
|
195
196
|
// Post-login logic (e.g., sync session to another store)
|
|
196
197
|
console.log('User logged in!', bundle.sessionPayload);
|
|
198
|
+
},
|
|
199
|
+
onError: (error, ctx) => {
|
|
200
|
+
// Handle error (e.g., log it or perform cleanup)
|
|
201
|
+
console.error('Login failed:', error.message);
|
|
197
202
|
}
|
|
198
203
|
});
|
|
199
204
|
|
|
200
205
|
await api.logout();
|
|
201
206
|
```
|
|
202
207
|
|
|
208
|
+
### Using Promises (.then, .catch, .finally)
|
|
209
|
+
|
|
210
|
+
All API methods return a Promise that resolves to an `APIResponse` object. You can use traditional promise chaining:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// Example with GET request
|
|
214
|
+
api.get('/me')
|
|
215
|
+
.then(({ data: user, status }) => {
|
|
216
|
+
console.log(`User ${user.name} fetched with status ${status}`);
|
|
217
|
+
})
|
|
218
|
+
.catch(err => {
|
|
219
|
+
console.error('Failed to fetch user:', err.message);
|
|
220
|
+
})
|
|
221
|
+
.finally(() => {
|
|
222
|
+
console.log('Request finished');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Example with login
|
|
226
|
+
api.login(credentials)
|
|
227
|
+
.then(({ data: token }) => {
|
|
228
|
+
console.log('Successfully logged in!', token.accessToken);
|
|
229
|
+
})
|
|
230
|
+
.catch(err => {
|
|
231
|
+
if (err instanceof AuthError) {
|
|
232
|
+
console.error('Authentication failed:', err.message);
|
|
233
|
+
} else {
|
|
234
|
+
console.error('An unexpected error occurred:', err.message);
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
.finally(() => {
|
|
238
|
+
// E.g. stop loading state
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
> **Note:** Since all methods return an `APIResponse` object, you can use destructuring in `.then()` to access the data directly, which allows for clean syntax like `.then(({ data: token }) => ... )`.
|
|
243
|
+
|
|
203
244
|
## License
|
|
204
245
|
|
|
205
246
|
MIT © [oamm](https://github.com/oamm)
|
package/dist/auth/detector.js
CHANGED
|
@@ -35,6 +35,13 @@ const EXPIRES_IN_FIELDS = [
|
|
|
35
35
|
'expiresIn',
|
|
36
36
|
'ttl',
|
|
37
37
|
];
|
|
38
|
+
/**
|
|
39
|
+
* Common field names for token type
|
|
40
|
+
*/
|
|
41
|
+
const TOKEN_TYPE_FIELDS = [
|
|
42
|
+
'token_type',
|
|
43
|
+
'tokenType',
|
|
44
|
+
];
|
|
38
45
|
/**
|
|
39
46
|
* Common field names for session payload
|
|
40
47
|
*/
|
|
@@ -97,10 +104,13 @@ export function autoDetectFields(body, fieldMapping) {
|
|
|
97
104
|
}
|
|
98
105
|
// Detect session payload (optional)
|
|
99
106
|
const sessionPayload = findField(SESSION_PAYLOAD_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.sessionPayload);
|
|
107
|
+
// Detect token type (optional)
|
|
108
|
+
const tokenType = findField(TOKEN_TYPE_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.tokenType);
|
|
100
109
|
return {
|
|
101
110
|
accessToken,
|
|
102
111
|
refreshToken,
|
|
103
112
|
accessExpiresAt,
|
|
113
|
+
tokenType: tokenType || undefined,
|
|
104
114
|
sessionPayload: sessionPayload || undefined,
|
|
105
115
|
};
|
|
106
116
|
}
|
|
@@ -114,6 +124,10 @@ export function parseJWTPayload(token) {
|
|
|
114
124
|
return null;
|
|
115
125
|
}
|
|
116
126
|
const payload = parts[1];
|
|
127
|
+
// Better UTF-8 support for environments with Buffer (like Node.js/Astro)
|
|
128
|
+
if (typeof Buffer !== 'undefined') {
|
|
129
|
+
return JSON.parse(Buffer.from(payload, 'base64').toString('utf8'));
|
|
130
|
+
}
|
|
117
131
|
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
118
132
|
return JSON.parse(decoded);
|
|
119
133
|
}
|
package/dist/auth/manager.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { APIResponse } from '../types';
|
|
1
2
|
import type { TokenBundle, Session, AuthConfig, TokenKitContext, AuthOptions, LoginOptions } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Token Manager handles all token operations
|
|
@@ -10,7 +11,7 @@ export declare class TokenManager {
|
|
|
10
11
|
/**
|
|
11
12
|
* Perform login
|
|
12
13
|
*/
|
|
13
|
-
login(ctx: TokenKitContext, credentials: any, options?: LoginOptions): Promise<TokenBundle
|
|
14
|
+
login(ctx: TokenKitContext, credentials: any, options?: LoginOptions): Promise<APIResponse<TokenBundle>>;
|
|
14
15
|
/**
|
|
15
16
|
* Perform token refresh
|
|
16
17
|
*/
|
|
@@ -39,4 +40,8 @@ export declare class TokenManager {
|
|
|
39
40
|
* Create flight key for single-flight deduplication
|
|
40
41
|
*/
|
|
41
42
|
private createFlightKey;
|
|
43
|
+
/**
|
|
44
|
+
* Join base URL and path safely
|
|
45
|
+
*/
|
|
46
|
+
private joinURL;
|
|
42
47
|
}
|
package/dist/auth/manager.js
CHANGED
|
@@ -54,7 +54,7 @@ export class TokenManager {
|
|
|
54
54
|
*/
|
|
55
55
|
login(ctx, credentials, options) {
|
|
56
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
-
const url = this.baseURL
|
|
57
|
+
const url = this.joinURL(this.baseURL, this.config.login);
|
|
58
58
|
const contentType = this.config.contentType || 'application/json';
|
|
59
59
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
60
60
|
const data = Object.assign(Object.assign(Object.assign({}, this.config.loginData), options === null || options === void 0 ? void 0 : options.data), credentials);
|
|
@@ -65,15 +65,25 @@ export class TokenManager {
|
|
|
65
65
|
else {
|
|
66
66
|
requestBody = JSON.stringify(data);
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
let response;
|
|
69
|
+
try {
|
|
70
|
+
response = yield fetch(url, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers,
|
|
73
|
+
body: requestBody,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const authError = new AuthError(`Login request failed: ${error.message}`);
|
|
78
|
+
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
79
|
+
yield options.onError(authError, ctx);
|
|
80
|
+
throw authError;
|
|
81
|
+
}
|
|
75
82
|
if (!response.ok) {
|
|
76
|
-
|
|
83
|
+
const authError = new AuthError(`Login failed: ${response.status} ${response.statusText}`, response.status, response);
|
|
84
|
+
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
85
|
+
yield options.onError(authError, ctx);
|
|
86
|
+
throw authError;
|
|
77
87
|
}
|
|
78
88
|
const body = yield response.json().catch(() => ({}));
|
|
79
89
|
// Parse response
|
|
@@ -84,7 +94,10 @@ export class TokenManager {
|
|
|
84
94
|
: autoDetectFields(body, this.config.fields);
|
|
85
95
|
}
|
|
86
96
|
catch (error) {
|
|
87
|
-
|
|
97
|
+
const authError = new AuthError(`Invalid login response: ${error.message}`, response.status, response);
|
|
98
|
+
if (options === null || options === void 0 ? void 0 : options.onError)
|
|
99
|
+
yield options.onError(authError, ctx);
|
|
100
|
+
throw authError;
|
|
88
101
|
}
|
|
89
102
|
// Store in cookies
|
|
90
103
|
storeTokens(ctx, bundle, this.config.cookies);
|
|
@@ -92,7 +105,14 @@ export class TokenManager {
|
|
|
92
105
|
if (options === null || options === void 0 ? void 0 : options.onLogin) {
|
|
93
106
|
yield options.onLogin(bundle, body, ctx);
|
|
94
107
|
}
|
|
95
|
-
return
|
|
108
|
+
return {
|
|
109
|
+
data: bundle,
|
|
110
|
+
status: response.status,
|
|
111
|
+
statusText: response.statusText,
|
|
112
|
+
headers: response.headers,
|
|
113
|
+
url: response.url,
|
|
114
|
+
ok: response.ok,
|
|
115
|
+
};
|
|
96
116
|
});
|
|
97
117
|
}
|
|
98
118
|
/**
|
|
@@ -114,7 +134,7 @@ export class TokenManager {
|
|
|
114
134
|
*/
|
|
115
135
|
performRefresh(ctx, refreshToken, options, extraHeaders) {
|
|
116
136
|
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
-
const url = this.baseURL
|
|
137
|
+
const url = this.joinURL(this.baseURL, this.config.refresh);
|
|
118
138
|
const contentType = this.config.contentType || 'application/json';
|
|
119
139
|
const headers = Object.assign(Object.assign({ 'Content-Type': contentType }, this.config.headers), extraHeaders);
|
|
120
140
|
const refreshField = this.config.refreshRequestField || 'refreshToken';
|
|
@@ -126,13 +146,17 @@ export class TokenManager {
|
|
|
126
146
|
else {
|
|
127
147
|
requestBody = JSON.stringify(data);
|
|
128
148
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
let response;
|
|
150
|
+
try {
|
|
151
|
+
response = yield fetch(url, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers,
|
|
154
|
+
body: requestBody,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
134
158
|
throw new AuthError(`Refresh request failed: ${error.message}`);
|
|
135
|
-
}
|
|
159
|
+
}
|
|
136
160
|
if (!response.ok) {
|
|
137
161
|
// 401/403 = invalid refresh token
|
|
138
162
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -166,7 +190,7 @@ export class TokenManager {
|
|
|
166
190
|
*/
|
|
167
191
|
ensure(ctx, options, headers) {
|
|
168
192
|
return __awaiter(this, void 0, void 0, function* () {
|
|
169
|
-
var _a, _b, _c, _d, _e;
|
|
193
|
+
var _a, _b, _c, _d, _e, _f;
|
|
170
194
|
const now = Math.floor(Date.now() / 1000);
|
|
171
195
|
const tokens = retrieveTokens(ctx, this.config.cookies);
|
|
172
196
|
// No tokens
|
|
@@ -182,6 +206,7 @@ export class TokenManager {
|
|
|
182
206
|
return {
|
|
183
207
|
accessToken: bundle.accessToken,
|
|
184
208
|
expiresAt: bundle.accessExpiresAt,
|
|
209
|
+
tokenType: bundle.tokenType,
|
|
185
210
|
payload: (_b = (_a = bundle.sessionPayload) !== null && _a !== void 0 ? _a : parseJWTPayload(bundle.accessToken)) !== null && _b !== void 0 ? _b : undefined,
|
|
186
211
|
};
|
|
187
212
|
}
|
|
@@ -193,6 +218,7 @@ export class TokenManager {
|
|
|
193
218
|
return {
|
|
194
219
|
accessToken: bundle.accessToken,
|
|
195
220
|
expiresAt: bundle.accessExpiresAt,
|
|
221
|
+
tokenType: bundle.tokenType,
|
|
196
222
|
payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
|
|
197
223
|
};
|
|
198
224
|
}
|
|
@@ -206,7 +232,8 @@ export class TokenManager {
|
|
|
206
232
|
return {
|
|
207
233
|
accessToken: tokens.accessToken,
|
|
208
234
|
expiresAt: tokens.expiresAt,
|
|
209
|
-
|
|
235
|
+
tokenType: (_e = tokens.tokenType) !== null && _e !== void 0 ? _e : undefined,
|
|
236
|
+
payload: (_f = parseJWTPayload(tokens.accessToken)) !== null && _f !== void 0 ? _f : undefined,
|
|
210
237
|
};
|
|
211
238
|
});
|
|
212
239
|
}
|
|
@@ -215,15 +242,22 @@ export class TokenManager {
|
|
|
215
242
|
*/
|
|
216
243
|
logout(ctx) {
|
|
217
244
|
return __awaiter(this, void 0, void 0, function* () {
|
|
245
|
+
var _a;
|
|
218
246
|
// Optionally call logout endpoint
|
|
219
247
|
if (this.config.logout) {
|
|
220
248
|
try {
|
|
221
|
-
const url = this.baseURL
|
|
222
|
-
|
|
249
|
+
const url = this.joinURL(this.baseURL, this.config.logout);
|
|
250
|
+
const session = this.getSession(ctx);
|
|
251
|
+
const headers = {};
|
|
252
|
+
if (session === null || session === void 0 ? void 0 : session.accessToken) {
|
|
253
|
+
const injectFn = (_a = this.config.injectToken) !== null && _a !== void 0 ? _a : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
|
|
254
|
+
headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
|
|
255
|
+
}
|
|
256
|
+
yield fetch(url, { method: 'POST', headers });
|
|
223
257
|
}
|
|
224
258
|
catch (error) {
|
|
225
259
|
// Ignore logout endpoint errors
|
|
226
|
-
console.warn('Logout endpoint failed:', error);
|
|
260
|
+
console.warn('[TokenKit] Logout endpoint failed:', error);
|
|
227
261
|
}
|
|
228
262
|
}
|
|
229
263
|
clearTokens(ctx, this.config.cookies);
|
|
@@ -233,7 +267,7 @@ export class TokenManager {
|
|
|
233
267
|
* Get current session (no refresh)
|
|
234
268
|
*/
|
|
235
269
|
getSession(ctx) {
|
|
236
|
-
var _a;
|
|
270
|
+
var _a, _b;
|
|
237
271
|
const tokens = retrieveTokens(ctx, this.config.cookies);
|
|
238
272
|
if (!tokens.accessToken || !tokens.expiresAt) {
|
|
239
273
|
return null;
|
|
@@ -241,7 +275,8 @@ export class TokenManager {
|
|
|
241
275
|
return {
|
|
242
276
|
accessToken: tokens.accessToken,
|
|
243
277
|
expiresAt: tokens.expiresAt,
|
|
244
|
-
|
|
278
|
+
tokenType: (_a = tokens.tokenType) !== null && _a !== void 0 ? _a : undefined,
|
|
279
|
+
payload: (_b = parseJWTPayload(tokens.accessToken)) !== null && _b !== void 0 ? _b : undefined,
|
|
245
280
|
};
|
|
246
281
|
}
|
|
247
282
|
/**
|
|
@@ -255,12 +290,15 @@ export class TokenManager {
|
|
|
255
290
|
* Create flight key for single-flight deduplication
|
|
256
291
|
*/
|
|
257
292
|
createFlightKey(token) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
293
|
+
// Avoid weak hashing of sensitive tokens
|
|
294
|
+
return `refresh_${token}`;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Join base URL and path safely
|
|
298
|
+
*/
|
|
299
|
+
joinURL(base, path) {
|
|
300
|
+
const b = base.endsWith('/') ? base : base + '/';
|
|
301
|
+
const p = path.startsWith('/') ? path.slice(1) : path;
|
|
302
|
+
return b + p;
|
|
265
303
|
}
|
|
266
304
|
}
|
package/dist/auth/policy.js
CHANGED
|
@@ -62,5 +62,6 @@ export function isExpired(expiresAt, now, policy = {}) {
|
|
|
62
62
|
const clockSkew = typeof normalized.clockSkew === 'number'
|
|
63
63
|
? normalized.clockSkew
|
|
64
64
|
: parseTime(normalized.clockSkew);
|
|
65
|
-
|
|
65
|
+
// Pessimistic: consider it expired if current time + skew is past expiration
|
|
66
|
+
return now + clockSkew > expiresAt;
|
|
66
67
|
}
|
package/dist/auth/storage.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface CookieNames {
|
|
|
7
7
|
refreshToken: string;
|
|
8
8
|
expiresAt: string;
|
|
9
9
|
lastRefreshAt: string;
|
|
10
|
+
tokenType: string;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Get cookie names with optional prefix
|
|
@@ -33,6 +34,7 @@ export declare function retrieveTokens(ctx: TokenKitContext, cookieConfig?: Cook
|
|
|
33
34
|
refreshToken: string | null;
|
|
34
35
|
expiresAt: number | null;
|
|
35
36
|
lastRefreshAt: number | null;
|
|
37
|
+
tokenType: string | null;
|
|
36
38
|
};
|
|
37
39
|
/**
|
|
38
40
|
* Clear all auth cookies
|
package/dist/auth/storage.js
CHANGED
|
@@ -9,6 +9,7 @@ export function getCookieNames(prefix) {
|
|
|
9
9
|
refreshToken: `${p}refresh_token`,
|
|
10
10
|
expiresAt: `${p}access_expires_at`,
|
|
11
11
|
lastRefreshAt: `${p}last_refresh_at`,
|
|
12
|
+
tokenType: `${p}token_type`,
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
@@ -44,20 +45,25 @@ export function storeTokens(ctx, bundle, cookieConfig = {}) {
|
|
|
44
45
|
ctx.cookies.set(names.expiresAt, bundle.accessExpiresAt.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
|
|
45
46
|
// Set last refresh timestamp
|
|
46
47
|
ctx.cookies.set(names.lastRefreshAt, now.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
|
|
48
|
+
// Set token type if available
|
|
49
|
+
if (bundle.tokenType) {
|
|
50
|
+
ctx.cookies.set(names.tokenType, bundle.tokenType, Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
|
|
51
|
+
}
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Retrieve tokens from cookies
|
|
50
55
|
*/
|
|
51
56
|
export function retrieveTokens(ctx, cookieConfig = {}) {
|
|
52
|
-
var _a, _b, _c, _d;
|
|
57
|
+
var _a, _b, _c, _d, _e;
|
|
53
58
|
const names = getCookieNames(cookieConfig.prefix);
|
|
54
59
|
const accessToken = ((_a = ctx.cookies.get(names.accessToken)) === null || _a === void 0 ? void 0 : _a.value) || null;
|
|
55
60
|
const refreshToken = ((_b = ctx.cookies.get(names.refreshToken)) === null || _b === void 0 ? void 0 : _b.value) || null;
|
|
56
|
-
const
|
|
61
|
+
const tokenType = ((_c = ctx.cookies.get(names.tokenType)) === null || _c === void 0 ? void 0 : _c.value) || null;
|
|
62
|
+
const expiresAtStr = (_d = ctx.cookies.get(names.expiresAt)) === null || _d === void 0 ? void 0 : _d.value;
|
|
57
63
|
const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null;
|
|
58
|
-
const lastRefreshAtStr = (
|
|
64
|
+
const lastRefreshAtStr = (_e = ctx.cookies.get(names.lastRefreshAt)) === null || _e === void 0 ? void 0 : _e.value;
|
|
59
65
|
const lastRefreshAt = lastRefreshAtStr ? parseInt(lastRefreshAtStr, 10) : null;
|
|
60
|
-
return { accessToken, refreshToken, expiresAt, lastRefreshAt };
|
|
66
|
+
return { accessToken, refreshToken, expiresAt, lastRefreshAt, tokenType };
|
|
61
67
|
}
|
|
62
68
|
/**
|
|
63
69
|
* Clear all auth cookies
|
|
@@ -69,4 +75,5 @@ export function clearTokens(ctx, cookieConfig = {}) {
|
|
|
69
75
|
ctx.cookies.delete(names.refreshToken, Object.assign(Object.assign({}, options), { path: '/' }));
|
|
70
76
|
ctx.cookies.delete(names.expiresAt, Object.assign(Object.assign({}, options), { path: '/' }));
|
|
71
77
|
ctx.cookies.delete(names.lastRefreshAt, Object.assign(Object.assign({}, options), { path: '/' }));
|
|
78
|
+
ctx.cookies.delete(names.tokenType, Object.assign(Object.assign({}, options), { path: '/' }));
|
|
72
79
|
}
|
package/dist/client/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions } from '../types';
|
|
1
|
+
import type { APIResponse, ClientConfig, RequestConfig, RequestOptions, Session, TokenKitConfig, LoginOptions, TokenBundle } from '../types';
|
|
2
2
|
import { TokenManager } from '../auth/manager';
|
|
3
3
|
/**
|
|
4
4
|
* API Client
|
|
@@ -25,27 +25,27 @@ export declare class APIClient {
|
|
|
25
25
|
/**
|
|
26
26
|
* GET request
|
|
27
27
|
*/
|
|
28
|
-
get<T = any>(url: string, options?: RequestOptions): Promise<T
|
|
28
|
+
get<T = any>(url: string, options?: RequestOptions): Promise<APIResponse<T>>;
|
|
29
29
|
/**
|
|
30
30
|
* POST request
|
|
31
31
|
*/
|
|
32
|
-
post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T
|
|
32
|
+
post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
|
|
33
33
|
/**
|
|
34
34
|
* PUT request
|
|
35
35
|
*/
|
|
36
|
-
put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T
|
|
36
|
+
put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
|
|
37
37
|
/**
|
|
38
38
|
* PATCH request
|
|
39
39
|
*/
|
|
40
|
-
patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T
|
|
40
|
+
patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<APIResponse<T>>;
|
|
41
41
|
/**
|
|
42
42
|
* DELETE request
|
|
43
43
|
*/
|
|
44
|
-
delete<T = any>(url: string, options?: RequestOptions): Promise<T
|
|
44
|
+
delete<T = any>(url: string, options?: RequestOptions): Promise<APIResponse<T>>;
|
|
45
45
|
/**
|
|
46
46
|
* Generic request method
|
|
47
47
|
*/
|
|
48
|
-
request<T = any>(config: RequestConfig): Promise<T
|
|
48
|
+
request<T = any>(config: RequestConfig): Promise<APIResponse<T>>;
|
|
49
49
|
/**
|
|
50
50
|
* Execute single request
|
|
51
51
|
*/
|
|
@@ -62,10 +62,14 @@ export declare class APIClient {
|
|
|
62
62
|
* Build request headers
|
|
63
63
|
*/
|
|
64
64
|
private buildHeaders;
|
|
65
|
+
/**
|
|
66
|
+
* Check if a URL is safe for token injection (same origin as baseURL)
|
|
67
|
+
*/
|
|
68
|
+
private isSafeURL;
|
|
65
69
|
/**
|
|
66
70
|
* Login
|
|
67
71
|
*/
|
|
68
|
-
login(credentials: any, options?: LoginOptions): Promise<
|
|
72
|
+
login(credentials: any, options?: LoginOptions): Promise<APIResponse<TokenBundle>>;
|
|
69
73
|
/**
|
|
70
74
|
* Logout
|
|
71
75
|
*/
|
package/dist/client/client.js
CHANGED
|
@@ -115,15 +115,12 @@ export class APIClient {
|
|
|
115
115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
116
116
|
const ctx = getContextStore();
|
|
117
117
|
let attempt = 0;
|
|
118
|
-
let lastError;
|
|
119
118
|
while (true) {
|
|
120
119
|
attempt++;
|
|
121
120
|
try {
|
|
122
|
-
|
|
123
|
-
return response.data;
|
|
121
|
+
return yield this.executeRequest(config, ctx, attempt);
|
|
124
122
|
}
|
|
125
123
|
catch (error) {
|
|
126
|
-
lastError = error;
|
|
127
124
|
// Check if we should retry
|
|
128
125
|
if (shouldRetry(error.status, attempt, this.config.retry)) {
|
|
129
126
|
const delay = calculateDelay(attempt, this.config.retry);
|
|
@@ -149,7 +146,7 @@ export class APIClient {
|
|
|
149
146
|
// Build full URL
|
|
150
147
|
const fullURL = this.buildURL(config.url, config.params);
|
|
151
148
|
// Build headers
|
|
152
|
-
const headers = this.buildHeaders(config, ctx);
|
|
149
|
+
const headers = this.buildHeaders(config, ctx, fullURL);
|
|
153
150
|
// Build request init
|
|
154
151
|
const init = {
|
|
155
152
|
method: config.method,
|
|
@@ -246,6 +243,7 @@ export class APIClient {
|
|
|
246
243
|
statusText: response.statusText,
|
|
247
244
|
headers: response.headers,
|
|
248
245
|
url,
|
|
246
|
+
ok: response.ok,
|
|
249
247
|
};
|
|
250
248
|
});
|
|
251
249
|
}
|
|
@@ -267,19 +265,33 @@ export class APIClient {
|
|
|
267
265
|
/**
|
|
268
266
|
* Build request headers
|
|
269
267
|
*/
|
|
270
|
-
buildHeaders(config, ctx) {
|
|
268
|
+
buildHeaders(config, ctx, targetURL) {
|
|
271
269
|
var _a, _b;
|
|
272
270
|
const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json' }, this.config.headers), config.headers);
|
|
273
|
-
// Add auth token if available
|
|
274
|
-
if (this.tokenManager && !config.skipAuth) {
|
|
271
|
+
// Add auth token if available (only for safe URLs)
|
|
272
|
+
if (this.tokenManager && !config.skipAuth && this.isSafeURL(targetURL)) {
|
|
275
273
|
const session = this.tokenManager.getSession(ctx);
|
|
276
274
|
if (session === null || session === void 0 ? void 0 : session.accessToken) {
|
|
277
|
-
const injectFn = (_b = (_a = this.config.auth) === null || _a === void 0 ? void 0 : _a.injectToken) !== null && _b !== void 0 ? _b : ((token) =>
|
|
278
|
-
headers['Authorization'] = injectFn(session.accessToken);
|
|
275
|
+
const injectFn = (_b = (_a = this.config.auth) === null || _a === void 0 ? void 0 : _a.injectToken) !== null && _b !== void 0 ? _b : ((token, type) => `${type !== null && type !== void 0 ? type : 'Bearer'} ${token}`);
|
|
276
|
+
headers['Authorization'] = injectFn(session.accessToken, session.tokenType);
|
|
279
277
|
}
|
|
280
278
|
}
|
|
281
279
|
return headers;
|
|
282
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Check if a URL is safe for token injection (same origin as baseURL)
|
|
283
|
+
*/
|
|
284
|
+
isSafeURL(url) {
|
|
285
|
+
try {
|
|
286
|
+
const requestUrl = new URL(url, this.config.baseURL);
|
|
287
|
+
const baseUrl = new URL(this.config.baseURL || 'http://localhost');
|
|
288
|
+
return requestUrl.origin === baseUrl.origin;
|
|
289
|
+
}
|
|
290
|
+
catch (_a) {
|
|
291
|
+
// Only allow relative paths if baseURL is missing or invalid
|
|
292
|
+
return !url.startsWith('http') && !url.startsWith('//');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
283
295
|
/**
|
|
284
296
|
* Login
|
|
285
297
|
*/
|
|
@@ -289,7 +301,7 @@ export class APIClient {
|
|
|
289
301
|
throw new Error('Auth is not configured for this client');
|
|
290
302
|
}
|
|
291
303
|
const context = getContextStore();
|
|
292
|
-
|
|
304
|
+
return this.tokenManager.login(context, credentials, options);
|
|
293
305
|
});
|
|
294
306
|
}
|
|
295
307
|
/**
|
package/dist/config.js
CHANGED
|
@@ -1,43 +1,63 @@
|
|
|
1
1
|
// packages/astro-tokenkit/src/config.ts
|
|
2
2
|
import { TokenManager } from "./auth/manager";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const CONFIG_KEY = Symbol.for('astro-tokenkit.config');
|
|
4
|
+
const MANAGER_KEY = Symbol.for('astro-tokenkit.manager');
|
|
5
|
+
const globalStorage = globalThis;
|
|
6
|
+
// Initialize global storage if not present
|
|
7
|
+
if (!globalStorage[CONFIG_KEY]) {
|
|
8
|
+
globalStorage[CONFIG_KEY] = {
|
|
9
|
+
runWithContext: undefined,
|
|
10
|
+
getContextStore: undefined,
|
|
11
|
+
setContextStore: undefined,
|
|
12
|
+
baseURL: "",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
10
15
|
/**
|
|
11
16
|
* Set configuration
|
|
12
17
|
*/
|
|
13
18
|
export function setConfig(userConfig) {
|
|
14
|
-
const
|
|
19
|
+
const currentConfig = globalStorage[CONFIG_KEY];
|
|
20
|
+
const finalConfig = Object.assign(Object.assign({}, currentConfig), userConfig);
|
|
15
21
|
// Validate that getter and setter are defined together
|
|
16
22
|
if ((finalConfig.getContextStore && !finalConfig.setContextStore) ||
|
|
17
23
|
(!finalConfig.getContextStore && finalConfig.setContextStore)) {
|
|
18
24
|
throw new Error("[TokenKit] getContextStore and setContextStore must be defined together.");
|
|
19
25
|
}
|
|
20
|
-
|
|
26
|
+
globalStorage[CONFIG_KEY] = finalConfig;
|
|
21
27
|
// Re-initialize global token manager if auth changed
|
|
22
|
-
if (
|
|
23
|
-
|
|
28
|
+
if (finalConfig.auth) {
|
|
29
|
+
globalStorage[MANAGER_KEY] = new TokenManager(finalConfig.auth, finalConfig.baseURL);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
globalStorage[MANAGER_KEY] = undefined;
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
36
|
* Get current configuration
|
|
28
37
|
*/
|
|
29
38
|
export function getConfig() {
|
|
30
|
-
return
|
|
39
|
+
return globalStorage[CONFIG_KEY];
|
|
31
40
|
}
|
|
32
41
|
/**
|
|
33
42
|
* Get global token manager
|
|
34
43
|
*/
|
|
35
44
|
export function getTokenManager() {
|
|
36
|
-
return
|
|
45
|
+
return globalStorage[MANAGER_KEY];
|
|
37
46
|
}
|
|
38
47
|
/**
|
|
39
48
|
* Set global token manager (mainly for testing)
|
|
40
49
|
*/
|
|
41
50
|
export function setTokenManager(manager) {
|
|
42
|
-
|
|
51
|
+
globalStorage[MANAGER_KEY] = manager;
|
|
52
|
+
}
|
|
53
|
+
// Handle injected configuration from Astro integration
|
|
54
|
+
try {
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
const injectedConfig = typeof __TOKENKIT_CONFIG__ !== 'undefined' ? __TOKENKIT_CONFIG__ : undefined;
|
|
57
|
+
if (injectedConfig) {
|
|
58
|
+
setConfig(injectedConfig);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
// Ignore errors in environments where __TOKENKIT_CONFIG__ might be restricted
|
|
43
63
|
}
|