@spidy092/auth-client 1.1.0 → 2.0.0
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/core.js +34 -9
- package/package.json +1 -1
- package/token.js +281 -58
package/core.js
CHANGED
|
@@ -214,31 +214,55 @@ export function resetCallbackState() {
|
|
|
214
214
|
console.log('🔄 Callback state reset');
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
// auth-client/core.js
|
|
217
218
|
export async function refreshToken() {
|
|
218
219
|
const { clientKey, authBaseUrl } = getConfig();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
const refreshTokenValue = getRefreshToken(); // ✅ Now checks both cookie & localStorage
|
|
221
|
+
|
|
222
|
+
console.log('🔄 Refreshing token:', {
|
|
223
|
+
clientKey,
|
|
224
|
+
mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
|
|
225
|
+
hasRefreshToken: !!refreshTokenValue
|
|
223
226
|
});
|
|
224
|
-
|
|
227
|
+
|
|
228
|
+
if (!refreshTokenValue) {
|
|
229
|
+
console.warn('⚠️ No refresh token available for refresh');
|
|
230
|
+
clearToken();
|
|
231
|
+
throw new Error('No refresh token available');
|
|
232
|
+
}
|
|
233
|
+
|
|
225
234
|
try {
|
|
226
235
|
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, {
|
|
227
236
|
method: 'POST',
|
|
228
|
-
credentials: 'include',
|
|
237
|
+
credentials: 'include', // ✅ Sends cookie if available
|
|
238
|
+
headers: {
|
|
239
|
+
'Content-Type': 'application/json',
|
|
240
|
+
'X-Refresh-Token': refreshTokenValue // ✅ Also send in header as fallback
|
|
241
|
+
},
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
refreshToken: refreshTokenValue // ✅ Also send in body
|
|
244
|
+
})
|
|
229
245
|
});
|
|
230
246
|
|
|
231
247
|
if (!response.ok) {
|
|
232
248
|
throw new Error('Refresh failed');
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
const { access_token } = await response.json();
|
|
236
|
-
|
|
251
|
+
const { access_token, refresh_token: new_refresh_token } = await response.json();
|
|
252
|
+
|
|
253
|
+
// ✅ Update access token (triggers listeners)
|
|
237
254
|
setToken(access_token);
|
|
255
|
+
|
|
256
|
+
// ✅ Update refresh token in BOTH storages if backend returned new one
|
|
257
|
+
if (new_refresh_token) {
|
|
258
|
+
setRefreshToken(new_refresh_token);
|
|
259
|
+
}
|
|
260
|
+
|
|
238
261
|
console.log('✅ Token refresh successful, listeners notified');
|
|
239
262
|
return access_token;
|
|
240
263
|
} catch (err) {
|
|
241
|
-
|
|
264
|
+
console.error('❌ Token refresh failed:', err);
|
|
265
|
+
// ✅ Clear everything on refresh failure
|
|
242
266
|
clearToken();
|
|
243
267
|
clearRefreshToken();
|
|
244
268
|
throw err;
|
|
@@ -246,6 +270,7 @@ export async function refreshToken() {
|
|
|
246
270
|
}
|
|
247
271
|
|
|
248
272
|
|
|
273
|
+
|
|
249
274
|
export async function validateCurrentSession() {
|
|
250
275
|
try {
|
|
251
276
|
const { authBaseUrl } = getConfig();
|
package/package.json
CHANGED
package/token.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
|
|
1
2
|
// auth-client/token.js
|
|
3
|
+
|
|
2
4
|
import { jwtDecode } from 'jwt-decode';
|
|
3
5
|
|
|
4
6
|
let accessToken = null;
|
|
5
7
|
const listeners = new Set();
|
|
6
8
|
|
|
7
9
|
const REFRESH_COOKIE = 'account_refresh_token';
|
|
10
|
+
const REFRESH_STORAGE_KEY = 'refresh_token'; // localStorage key
|
|
8
11
|
const COOKIE_MAX_AGE = 7 * 24 * 60 * 60; // 7 days in seconds
|
|
9
12
|
|
|
10
13
|
function secureAttribute() {
|
|
@@ -17,6 +20,7 @@ function secureAttribute() {
|
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
// ========== ACCESS TOKEN (localStorage only) ==========
|
|
20
24
|
function writeAccessToken(token) {
|
|
21
25
|
if (!token) {
|
|
22
26
|
try {
|
|
@@ -43,22 +47,95 @@ function readAccessToken() {
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
// ========== REFRESH TOKEN (localStorage + Cookie dual storage) ==========
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Store refresh token in BOTH localStorage AND cookie
|
|
54
|
+
* Whichever survives (cross-domain, privacy settings) will work
|
|
55
|
+
*/
|
|
56
|
+
export function setRefreshToken(token) {
|
|
57
|
+
if (!token) {
|
|
58
|
+
clearRefreshToken();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
|
|
63
|
+
|
|
64
|
+
// 1. Try to set cookie (works for same-domain, cross-subdomain)
|
|
47
65
|
try {
|
|
48
|
-
|
|
66
|
+
document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax${secureAttribute()}; Expires=${expires.toUTCString()}`;
|
|
67
|
+
console.log('✅ Refresh token stored in cookie');
|
|
49
68
|
} catch (err) {
|
|
50
|
-
|
|
69
|
+
console.warn('⚠️ Could not persist refresh token cookie:', err);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Also store in localStorage as backup (survives browser privacy settings)
|
|
73
|
+
try {
|
|
74
|
+
localStorage.setItem(REFRESH_STORAGE_KEY, token);
|
|
75
|
+
console.log('✅ Refresh token stored in localStorage');
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.warn('⚠️ Could not persist refresh token to localStorage:', err);
|
|
51
78
|
}
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Get refresh token from cookie OR localStorage (whichever works)
|
|
83
|
+
* Priority: Cookie > localStorage
|
|
84
|
+
*/
|
|
85
|
+
export function getRefreshToken() {
|
|
86
|
+
// 1. Try cookie first (preferred for httpOnly scenario)
|
|
87
|
+
let cookieMatch = null;
|
|
88
|
+
try {
|
|
89
|
+
cookieMatch = document.cookie
|
|
90
|
+
?.split('; ')
|
|
91
|
+
?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
|
|
92
|
+
} catch (err) {
|
|
93
|
+
cookieMatch = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (cookieMatch) {
|
|
97
|
+
const token = decodeURIComponent(cookieMatch.split('=')[1]);
|
|
98
|
+
console.log('✅ Retrieved refresh token from cookie');
|
|
99
|
+
return token;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 2. Fallback to localStorage
|
|
103
|
+
try {
|
|
104
|
+
const token = localStorage.getItem(REFRESH_STORAGE_KEY);
|
|
105
|
+
if (token) {
|
|
106
|
+
console.log('✅ Retrieved refresh token from localStorage (fallback)');
|
|
107
|
+
return token;
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.warn('⚠️ Could not read refresh token from localStorage:', err);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.warn('⚠️ No refresh token found in cookie or localStorage');
|
|
114
|
+
return null;
|
|
60
115
|
}
|
|
61
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Clear refresh token from BOTH cookie AND localStorage
|
|
119
|
+
*/
|
|
120
|
+
export function clearRefreshToken() {
|
|
121
|
+
// Clear cookie
|
|
122
|
+
try {
|
|
123
|
+
document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Lax${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
124
|
+
console.log('✅ Cleared refresh token cookie');
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn('⚠️ Could not clear refresh token cookie:', err);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Clear localStorage
|
|
130
|
+
try {
|
|
131
|
+
localStorage.removeItem(REFRESH_STORAGE_KEY);
|
|
132
|
+
console.log('✅ Cleared refresh token from localStorage');
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn('⚠️ Could not clear refresh token from localStorage:', err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ========== ACCESS TOKEN MANAGEMENT ==========
|
|
62
139
|
export function setToken(token) {
|
|
63
140
|
const previousToken = accessToken;
|
|
64
141
|
accessToken = token || null;
|
|
@@ -102,61 +179,21 @@ export function clearToken() {
|
|
|
102
179
|
});
|
|
103
180
|
}
|
|
104
181
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
clearRefreshToken();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
|
|
112
|
-
try {
|
|
113
|
-
document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Strict${secureAttribute()}; Expires=${expires.toUTCString()}`;
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.warn('Could not persist refresh token cookie:', err);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
sessionStorage.setItem(REFRESH_COOKIE, token);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
console.warn('Could not persist refresh token to sessionStorage:', err);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function getRefreshToken() {
|
|
126
|
-
// Prefer cookie to align with server expectations
|
|
127
|
-
let cookieMatch = null;
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
cookieMatch = document.cookie
|
|
131
|
-
?.split('; ')
|
|
132
|
-
?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
|
|
133
|
-
} catch (err) {
|
|
134
|
-
cookieMatch = null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (cookieMatch) {
|
|
138
|
-
return decodeURIComponent(cookieMatch.split('=')[1]);
|
|
139
|
-
}
|
|
140
|
-
|
|
182
|
+
// ========== HELPER FUNCTIONS ==========
|
|
183
|
+
function decode(token) {
|
|
141
184
|
try {
|
|
142
|
-
return
|
|
185
|
+
return jwtDecode(token);
|
|
143
186
|
} catch (err) {
|
|
144
|
-
console.warn('Could not read refresh token from sessionStorage:', err);
|
|
145
187
|
return null;
|
|
146
188
|
}
|
|
147
189
|
}
|
|
148
190
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
sessionStorage.removeItem(REFRESH_COOKIE);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
console.warn('Could not clear refresh token from sessionStorage:', err);
|
|
159
|
-
}
|
|
191
|
+
function isExpired(token, bufferSeconds = 60) {
|
|
192
|
+
if (!token) return true;
|
|
193
|
+
const decoded = decode(token);
|
|
194
|
+
if (!decoded?.exp) return true;
|
|
195
|
+
const now = Date.now() / 1000;
|
|
196
|
+
return decoded.exp < now + bufferSeconds;
|
|
160
197
|
}
|
|
161
198
|
|
|
162
199
|
export function addTokenListener(listener) {
|
|
@@ -182,3 +219,189 @@ export function isAuthenticated() {
|
|
|
182
219
|
return !!token && !isExpired(token, 15);
|
|
183
220
|
}
|
|
184
221
|
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
// // auth-client/token.js
|
|
225
|
+
// import { jwtDecode } from 'jwt-decode';
|
|
226
|
+
|
|
227
|
+
// let accessToken = null;
|
|
228
|
+
// const listeners = new Set();
|
|
229
|
+
|
|
230
|
+
// const REFRESH_COOKIE = 'account_refresh_token';
|
|
231
|
+
// const COOKIE_MAX_AGE = 7 * 24 * 60 * 60; // 7 days in seconds
|
|
232
|
+
|
|
233
|
+
// function secureAttribute() {
|
|
234
|
+
// try {
|
|
235
|
+
// return typeof window !== 'undefined' && window.location?.protocol === 'https:'
|
|
236
|
+
// ? '; Secure'
|
|
237
|
+
// : '';
|
|
238
|
+
// } catch (err) {
|
|
239
|
+
// return '';
|
|
240
|
+
// }
|
|
241
|
+
// }
|
|
242
|
+
|
|
243
|
+
// function writeAccessToken(token) {
|
|
244
|
+
// if (!token) {
|
|
245
|
+
// try {
|
|
246
|
+
// localStorage.removeItem('authToken');
|
|
247
|
+
// } catch (err) {
|
|
248
|
+
// console.warn('Could not clear token from localStorage:', err);
|
|
249
|
+
// }
|
|
250
|
+
// return;
|
|
251
|
+
// }
|
|
252
|
+
|
|
253
|
+
// try {
|
|
254
|
+
// localStorage.setItem('authToken', token);
|
|
255
|
+
// } catch (err) {
|
|
256
|
+
// console.warn('Could not persist token to localStorage:', err);
|
|
257
|
+
// }
|
|
258
|
+
// }
|
|
259
|
+
|
|
260
|
+
// function readAccessToken() {
|
|
261
|
+
// try {
|
|
262
|
+
// return localStorage.getItem('authToken');
|
|
263
|
+
// } catch (err) {
|
|
264
|
+
// console.warn('Could not read token from localStorage:', err);
|
|
265
|
+
// return null;
|
|
266
|
+
// }
|
|
267
|
+
// }
|
|
268
|
+
|
|
269
|
+
// function decode(token) {
|
|
270
|
+
// try {
|
|
271
|
+
// return jwtDecode(token);
|
|
272
|
+
// } catch (err) {
|
|
273
|
+
// return null;
|
|
274
|
+
// }
|
|
275
|
+
// }
|
|
276
|
+
|
|
277
|
+
// function isExpired(token, bufferSeconds = 60) {
|
|
278
|
+
// if (!token) return true;
|
|
279
|
+
// const decoded = decode(token);
|
|
280
|
+
// if (!decoded?.exp) return true;
|
|
281
|
+
// const now = Date.now() / 1000;
|
|
282
|
+
// return decoded.exp < now + bufferSeconds;
|
|
283
|
+
// }
|
|
284
|
+
|
|
285
|
+
// export function setToken(token) {
|
|
286
|
+
// const previousToken = accessToken;
|
|
287
|
+
// accessToken = token || null;
|
|
288
|
+
// writeAccessToken(accessToken);
|
|
289
|
+
|
|
290
|
+
// if (previousToken !== accessToken) {
|
|
291
|
+
// listeners.forEach((listener) => {
|
|
292
|
+
// try {
|
|
293
|
+
// listener(accessToken, previousToken);
|
|
294
|
+
// } catch (err) {
|
|
295
|
+
// console.warn('Token listener error:', err);
|
|
296
|
+
// }
|
|
297
|
+
// });
|
|
298
|
+
// }
|
|
299
|
+
// }
|
|
300
|
+
|
|
301
|
+
// export function getToken() {
|
|
302
|
+
// if (accessToken) return accessToken;
|
|
303
|
+
// accessToken = readAccessToken();
|
|
304
|
+
// return accessToken;
|
|
305
|
+
// }
|
|
306
|
+
|
|
307
|
+
// export function clearToken() {
|
|
308
|
+
// if (!accessToken) {
|
|
309
|
+
// writeAccessToken(null);
|
|
310
|
+
// clearRefreshToken();
|
|
311
|
+
// return;
|
|
312
|
+
// }
|
|
313
|
+
|
|
314
|
+
// const previousToken = accessToken;
|
|
315
|
+
// accessToken = null;
|
|
316
|
+
// writeAccessToken(null);
|
|
317
|
+
// clearRefreshToken();
|
|
318
|
+
|
|
319
|
+
// listeners.forEach((listener) => {
|
|
320
|
+
// try {
|
|
321
|
+
// listener(null, previousToken);
|
|
322
|
+
// } catch (err) {
|
|
323
|
+
// console.warn('Token listener error:', err);
|
|
324
|
+
// }
|
|
325
|
+
// });
|
|
326
|
+
// }
|
|
327
|
+
|
|
328
|
+
// export function setRefreshToken(token) {
|
|
329
|
+
// if (!token) {
|
|
330
|
+
// clearRefreshToken();
|
|
331
|
+
// return;
|
|
332
|
+
// }
|
|
333
|
+
|
|
334
|
+
// const expires = new Date(Date.now() + COOKIE_MAX_AGE * 1000);
|
|
335
|
+
// try {
|
|
336
|
+
// document.cookie = `${REFRESH_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Strict${secureAttribute()}; Expires=${expires.toUTCString()}`;
|
|
337
|
+
// } catch (err) {
|
|
338
|
+
// console.warn('Could not persist refresh token cookie:', err);
|
|
339
|
+
// }
|
|
340
|
+
|
|
341
|
+
// try {
|
|
342
|
+
// sessionStorage.setItem(REFRESH_COOKIE, token);
|
|
343
|
+
// } catch (err) {
|
|
344
|
+
// console.warn('Could not persist refresh token to sessionStorage:', err);
|
|
345
|
+
// }
|
|
346
|
+
// }
|
|
347
|
+
|
|
348
|
+
// export function getRefreshToken() {
|
|
349
|
+
// // Prefer cookie to align with server expectations
|
|
350
|
+
// let cookieMatch = null;
|
|
351
|
+
|
|
352
|
+
// try {
|
|
353
|
+
// cookieMatch = document.cookie
|
|
354
|
+
// ?.split('; ')
|
|
355
|
+
// ?.find((row) => row.startsWith(`${REFRESH_COOKIE}=`));
|
|
356
|
+
// } catch (err) {
|
|
357
|
+
// cookieMatch = null;
|
|
358
|
+
// }
|
|
359
|
+
|
|
360
|
+
// if (cookieMatch) {
|
|
361
|
+
// return decodeURIComponent(cookieMatch.split('=')[1]);
|
|
362
|
+
// }
|
|
363
|
+
|
|
364
|
+
// try {
|
|
365
|
+
// return sessionStorage.getItem(REFRESH_COOKIE);
|
|
366
|
+
// } catch (err) {
|
|
367
|
+
// console.warn('Could not read refresh token from sessionStorage:', err);
|
|
368
|
+
// return null;
|
|
369
|
+
// }
|
|
370
|
+
// }
|
|
371
|
+
|
|
372
|
+
// export function clearRefreshToken() {
|
|
373
|
+
// try {
|
|
374
|
+
// document.cookie = `${REFRESH_COOKIE}=; Path=/; SameSite=Strict${secureAttribute()}; Expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
375
|
+
// } catch (err) {
|
|
376
|
+
// console.warn('Could not clear refresh token cookie:', err);
|
|
377
|
+
// }
|
|
378
|
+
// try {
|
|
379
|
+
// sessionStorage.removeItem(REFRESH_COOKIE);
|
|
380
|
+
// } catch (err) {
|
|
381
|
+
// console.warn('Could not clear refresh token from sessionStorage:', err);
|
|
382
|
+
// }
|
|
383
|
+
// }
|
|
384
|
+
|
|
385
|
+
// export function addTokenListener(listener) {
|
|
386
|
+
// if (typeof listener !== 'function') {
|
|
387
|
+
// throw new Error('Token listener must be a function');
|
|
388
|
+
// }
|
|
389
|
+
// listeners.add(listener);
|
|
390
|
+
// return () => {
|
|
391
|
+
// listeners.delete(listener);
|
|
392
|
+
// };
|
|
393
|
+
// }
|
|
394
|
+
|
|
395
|
+
// export function removeTokenListener(listener) {
|
|
396
|
+
// listeners.delete(listener);
|
|
397
|
+
// }
|
|
398
|
+
|
|
399
|
+
// export function getListenerCount() {
|
|
400
|
+
// return listeners.size;
|
|
401
|
+
// }
|
|
402
|
+
|
|
403
|
+
// export function isAuthenticated() {
|
|
404
|
+
// const token = getToken();
|
|
405
|
+
// return !!token && !isExpired(token, 15);
|
|
406
|
+
// }
|
|
407
|
+
|