@spidy092/auth-client 2.1.7 โ 3.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/Readme.md +9 -0
- package/dist/api.cjs +329 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.js +299 -0
- package/dist/api.js.map +1 -0
- package/dist/index.cjs +1047 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1008 -0
- package/dist/index.js.map +1 -0
- package/dist/react/AuthProvider.cjs +692 -0
- package/dist/react/AuthProvider.cjs.map +1 -0
- package/dist/react/AuthProvider.js +657 -0
- package/dist/react/AuthProvider.js.map +1 -0
- package/dist/react/useAuth.cjs +86 -0
- package/dist/react/useAuth.cjs.map +1 -0
- package/dist/react/useAuth.js +52 -0
- package/dist/react/useAuth.js.map +1 -0
- package/dist/react/useSessionMonitor.cjs +926 -0
- package/dist/react/useSessionMonitor.cjs.map +1 -0
- package/dist/react/useSessionMonitor.js +892 -0
- package/dist/react/useSessionMonitor.js.map +1 -0
- package/dist/utils/jwt.cjs +72 -0
- package/dist/utils/jwt.cjs.map +1 -0
- package/dist/utils/jwt.js +46 -0
- package/dist/utils/jwt.js.map +1 -0
- package/package.json +34 -13
- package/api.js +0 -95
- package/config.js +0 -63
- package/core.js +0 -570
- package/index.js +0 -106
- package/react/AuthProvider.jsx +0 -150
- package/react/useAuth.js +0 -10
- package/react/useSessionMonitor.js +0 -121
- package/token.js +0 -455
- package/utils/jwt.js +0 -27
package/core.js
DELETED
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
// auth-client/core.js - MINIMAL WORKING VERSION
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
setToken,
|
|
5
|
-
clearToken,
|
|
6
|
-
getToken,
|
|
7
|
-
setRefreshToken,
|
|
8
|
-
getRefreshToken,
|
|
9
|
-
clearRefreshToken,
|
|
10
|
-
} from './token';
|
|
11
|
-
import { getConfig, isRouterMode } from './config';
|
|
12
|
-
|
|
13
|
-
let callbackProcessed = false;
|
|
14
|
-
|
|
15
|
-
export function login(clientKeyArg, redirectUriArg) {
|
|
16
|
-
// โ
Reset callback state when starting new login
|
|
17
|
-
resetCallbackState();
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
clientKey: defaultClientKey,
|
|
21
|
-
authBaseUrl,
|
|
22
|
-
redirectUri: defaultRedirectUri,
|
|
23
|
-
accountUiUrl
|
|
24
|
-
} = getConfig();
|
|
25
|
-
|
|
26
|
-
const clientKey = clientKeyArg || defaultClientKey;
|
|
27
|
-
const redirectUri = redirectUriArg || defaultRedirectUri;
|
|
28
|
-
|
|
29
|
-
console.log('๐ Smart Login initiated:', {
|
|
30
|
-
mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
|
|
31
|
-
clientKey,
|
|
32
|
-
redirectUri
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!clientKey || !redirectUri) {
|
|
36
|
-
throw new Error('Missing clientKey or redirectUri');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
sessionStorage.setItem('originalApp', clientKey);
|
|
40
|
-
sessionStorage.setItem('returnUrl', redirectUri);
|
|
41
|
-
|
|
42
|
-
if (isRouterMode()) {
|
|
43
|
-
// Router mode: Direct backend authentication
|
|
44
|
-
return routerLogin(clientKey, redirectUri);
|
|
45
|
-
} else {
|
|
46
|
-
// Client mode: Redirect to centralized login
|
|
47
|
-
return clientLogin(clientKey, redirectUri);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// โ
Router mode: Direct backend call
|
|
52
|
-
function routerLogin(clientKey, redirectUri) {
|
|
53
|
-
const { authBaseUrl } = getConfig();
|
|
54
|
-
|
|
55
|
-
const params = new URLSearchParams();
|
|
56
|
-
if (redirectUri) {
|
|
57
|
-
params.append('redirect_uri', redirectUri);
|
|
58
|
-
}
|
|
59
|
-
const query = params.toString();
|
|
60
|
-
const backendLoginUrl = `${authBaseUrl}/login/${clientKey}${query ? `?${query}` : ''}`;
|
|
61
|
-
|
|
62
|
-
console.log('๐ญ Router Login: Direct backend authentication', {
|
|
63
|
-
clientKey,
|
|
64
|
-
redirectUri,
|
|
65
|
-
backendUrl: backendLoginUrl
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
window.location.href = backendLoginUrl;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// โ
Client mode: Centralized login
|
|
72
|
-
function clientLogin(clientKey, redirectUri) {
|
|
73
|
-
const { accountUiUrl } = getConfig();
|
|
74
|
-
|
|
75
|
-
const params = new URLSearchParams({
|
|
76
|
-
client: clientKey
|
|
77
|
-
});
|
|
78
|
-
if (redirectUri) {
|
|
79
|
-
params.append('redirect_uri', redirectUri);
|
|
80
|
-
}
|
|
81
|
-
const centralizedLoginUrl = `${accountUiUrl}/login?${params.toString()}`;
|
|
82
|
-
|
|
83
|
-
console.log('๐ Client Login: Redirecting to centralized login', {
|
|
84
|
-
clientKey,
|
|
85
|
-
redirectUri,
|
|
86
|
-
centralizedUrl: centralizedLoginUrl
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
window.location.href = centralizedLoginUrl;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function logout() {
|
|
93
|
-
resetCallbackState();
|
|
94
|
-
|
|
95
|
-
const { clientKey, authBaseUrl, accountUiUrl } = getConfig();
|
|
96
|
-
const token = getToken();
|
|
97
|
-
|
|
98
|
-
console.log('๐ช Smart Logout initiated');
|
|
99
|
-
|
|
100
|
-
clearToken();
|
|
101
|
-
clearRefreshToken();
|
|
102
|
-
sessionStorage.removeItem('originalApp');
|
|
103
|
-
sessionStorage.removeItem('returnUrl');
|
|
104
|
-
|
|
105
|
-
if (isRouterMode()) {
|
|
106
|
-
return routerLogout(clientKey, authBaseUrl, accountUiUrl, token);
|
|
107
|
-
} else {
|
|
108
|
-
return clientLogout(clientKey, accountUiUrl);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function routerLogout(clientKey, authBaseUrl, accountUiUrl, token) {
|
|
113
|
-
console.log('๐ญ Router Logout');
|
|
114
|
-
|
|
115
|
-
const refreshToken = getRefreshToken();
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const response = await fetch(`${authBaseUrl}/logout/${clientKey}`, {
|
|
119
|
-
method: 'POST',
|
|
120
|
-
credentials: 'include',
|
|
121
|
-
headers: {
|
|
122
|
-
'Authorization': token ? `Bearer ${token}` : '',
|
|
123
|
-
'Content-Type': 'application/json'
|
|
124
|
-
},
|
|
125
|
-
body: JSON.stringify({
|
|
126
|
-
refreshToken: refreshToken
|
|
127
|
-
})
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const data = await response.json();
|
|
131
|
-
console.log('โ
Logout response:', data);
|
|
132
|
-
|
|
133
|
-
clearRefreshToken();
|
|
134
|
-
clearToken();
|
|
135
|
-
|
|
136
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
137
|
-
|
|
138
|
-
if (data.success && data.keycloakLogoutUrl) {
|
|
139
|
-
window.location.href = data.keycloakLogoutUrl;
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.warn('โ ๏ธ Logout failed:', error);
|
|
145
|
-
clearRefreshToken();
|
|
146
|
-
clearToken();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
150
|
-
window.location.href = '/login';
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function clientLogout(clientKey, accountUiUrl) {
|
|
154
|
-
console.log('๐ Client Logout');
|
|
155
|
-
const logoutUrl = `${accountUiUrl}/login?client=${clientKey}&logout=true`;
|
|
156
|
-
window.location.href = logoutUrl;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function handleCallback() {
|
|
160
|
-
const params = new URLSearchParams(window.location.search);
|
|
161
|
-
const accessToken = params.get('access_token');
|
|
162
|
-
const error = params.get('error');
|
|
163
|
-
|
|
164
|
-
console.log('๐ Callback handling:', {
|
|
165
|
-
hasAccessToken: !!accessToken,
|
|
166
|
-
error
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// โ
Prevent duplicate callback processing
|
|
170
|
-
if (callbackProcessed) {
|
|
171
|
-
const existingToken = getToken();
|
|
172
|
-
if (existingToken) {
|
|
173
|
-
console.log('โ
Callback already processed, returning existing token');
|
|
174
|
-
return existingToken;
|
|
175
|
-
}
|
|
176
|
-
// Reset if no token found (might be a retry)
|
|
177
|
-
callbackProcessed = false;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
callbackProcessed = true;
|
|
181
|
-
sessionStorage.removeItem('originalApp');
|
|
182
|
-
sessionStorage.removeItem('returnUrl');
|
|
183
|
-
|
|
184
|
-
if (error) {
|
|
185
|
-
const errorDescription = params.get('error_description') || error;
|
|
186
|
-
throw new Error(`Authentication failed: ${errorDescription}`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (accessToken) {
|
|
190
|
-
setToken(accessToken);
|
|
191
|
-
|
|
192
|
-
// โ
For HTTP development, store refresh token from URL
|
|
193
|
-
// In HTTPS production, refresh token is in httpOnly cookie (more secure)
|
|
194
|
-
const refreshTokenInUrl = params.get('refresh_token');
|
|
195
|
-
if (refreshTokenInUrl) {
|
|
196
|
-
const isHttpDev = typeof window !== 'undefined' && window.location?.protocol === 'http:';
|
|
197
|
-
if (isHttpDev) {
|
|
198
|
-
console.log('๐ฆ HTTP dev mode: Storing refresh token from callback URL');
|
|
199
|
-
setRefreshToken(refreshTokenInUrl);
|
|
200
|
-
} else {
|
|
201
|
-
console.log('๐ HTTPS mode: Refresh token is in httpOnly cookie (ignoring URL param)');
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const url = new URL(window.location);
|
|
206
|
-
url.searchParams.delete('access_token');
|
|
207
|
-
url.searchParams.delete('refresh_token');
|
|
208
|
-
url.searchParams.delete('state');
|
|
209
|
-
url.searchParams.delete('error');
|
|
210
|
-
url.searchParams.delete('error_description');
|
|
211
|
-
window.history.replaceState({}, '', url);
|
|
212
|
-
|
|
213
|
-
console.log('โ
Callback processed successfully, token stored');
|
|
214
|
-
return accessToken;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
throw new Error('No access token found in callback URL');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function resetCallbackState() {
|
|
221
|
-
callbackProcessed = false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// โ
Add refresh lock to prevent concurrent refresh calls
|
|
225
|
-
let refreshInProgress = false;
|
|
226
|
-
let refreshPromise = null;
|
|
227
|
-
|
|
228
|
-
export async function refreshToken() {
|
|
229
|
-
const { clientKey, authBaseUrl } = getConfig();
|
|
230
|
-
|
|
231
|
-
// โ
Prevent concurrent refresh calls
|
|
232
|
-
if (refreshInProgress && refreshPromise) {
|
|
233
|
-
console.log('๐ Token refresh already in progress, waiting...');
|
|
234
|
-
return refreshPromise;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
refreshInProgress = true;
|
|
238
|
-
refreshPromise = (async () => {
|
|
239
|
-
try {
|
|
240
|
-
// Get stored refresh token (for HTTP development)
|
|
241
|
-
const storedRefreshToken = getRefreshToken();
|
|
242
|
-
|
|
243
|
-
console.log('๐ Refreshing token:', {
|
|
244
|
-
clientKey,
|
|
245
|
-
mode: isRouterMode() ? 'ROUTER' : 'CLIENT',
|
|
246
|
-
hasStoredRefreshToken: !!storedRefreshToken
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// Build request options - send refresh token in body and header for HTTP dev
|
|
250
|
-
const requestOptions = {
|
|
251
|
-
method: 'POST',
|
|
252
|
-
credentials: 'include', // โ
Include httpOnly cookies (for HTTPS)
|
|
253
|
-
headers: {
|
|
254
|
-
'Content-Type': 'application/json'
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
// For HTTP development, send refresh token in body and header
|
|
259
|
-
if (storedRefreshToken) {
|
|
260
|
-
requestOptions.headers['X-Refresh-Token'] = storedRefreshToken;
|
|
261
|
-
requestOptions.body = JSON.stringify({ refreshToken: storedRefreshToken });
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const response = await fetch(`${authBaseUrl}/refresh/${clientKey}`, requestOptions);
|
|
265
|
-
|
|
266
|
-
if (!response.ok) {
|
|
267
|
-
const errorText = await response.text();
|
|
268
|
-
console.error('โ Token refresh failed:', response.status, errorText);
|
|
269
|
-
throw new Error(`Refresh failed: ${response.status}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const data = await response.json();
|
|
273
|
-
const { access_token, refresh_token: new_refresh_token } = data;
|
|
274
|
-
|
|
275
|
-
if (!access_token) {
|
|
276
|
-
throw new Error('No access token in refresh response');
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// โ
This will trigger token listeners
|
|
280
|
-
setToken(access_token);
|
|
281
|
-
|
|
282
|
-
// โ
Store new refresh token if provided (token rotation)
|
|
283
|
-
if (new_refresh_token) {
|
|
284
|
-
setRefreshToken(new_refresh_token);
|
|
285
|
-
console.log('๐ New refresh token stored from rotation');
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
console.log('โ
Token refresh successful, listeners notified');
|
|
289
|
-
return access_token;
|
|
290
|
-
} catch (err) {
|
|
291
|
-
console.error('โ Token refresh error:', err);
|
|
292
|
-
// โ
This will trigger token listeners
|
|
293
|
-
clearToken();
|
|
294
|
-
clearRefreshToken();
|
|
295
|
-
throw err;
|
|
296
|
-
} finally {
|
|
297
|
-
refreshInProgress = false;
|
|
298
|
-
refreshPromise = null;
|
|
299
|
-
}
|
|
300
|
-
})();
|
|
301
|
-
|
|
302
|
-
return refreshPromise;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export async function validateCurrentSession() {
|
|
306
|
-
try {
|
|
307
|
-
const { authBaseUrl } = getConfig();
|
|
308
|
-
const token = getToken();
|
|
309
|
-
|
|
310
|
-
if (!token || !authBaseUrl) {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const response = await fetch(`${authBaseUrl}/account/validate-session`, {
|
|
315
|
-
method: 'GET',
|
|
316
|
-
headers: {
|
|
317
|
-
'Authorization': `Bearer ${token}`,
|
|
318
|
-
'Content-Type': 'application/json'
|
|
319
|
-
},
|
|
320
|
-
credentials: 'include'
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
if (!response.ok) {
|
|
324
|
-
if (response.status === 401) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const data = await response.json();
|
|
331
|
-
return data.valid === true;
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.warn('Session validation failed:', error.message);
|
|
334
|
-
if (error.message.includes('401')) {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
throw error;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ========== SESSION SECURITY: PROACTIVE REFRESH & VALIDATION ==========
|
|
342
|
-
// These functions ensure that:
|
|
343
|
-
// 1. Tokens are refreshed before they expire (proactive refresh)
|
|
344
|
-
// 2. Sessions deleted in Keycloak Admin UI are detected quickly (periodic validation)
|
|
345
|
-
|
|
346
|
-
let proactiveRefreshTimer = null;
|
|
347
|
-
let sessionValidationTimer = null;
|
|
348
|
-
let visibilityHandler = null;
|
|
349
|
-
let sessionInvalidCallbacks = new Set();
|
|
350
|
-
|
|
351
|
-
// Register a callback to be called when session is invalidated
|
|
352
|
-
export function onSessionInvalid(callback) {
|
|
353
|
-
if (typeof callback === 'function') {
|
|
354
|
-
sessionInvalidCallbacks.add(callback);
|
|
355
|
-
}
|
|
356
|
-
return () => sessionInvalidCallbacks.delete(callback);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Notify all registered callbacks that session is invalid
|
|
360
|
-
function notifySessionInvalid(reason = 'session_deleted') {
|
|
361
|
-
console.log('๐จ Session invalidated:', reason);
|
|
362
|
-
sessionInvalidCallbacks.forEach(callback => {
|
|
363
|
-
try {
|
|
364
|
-
callback(reason);
|
|
365
|
-
} catch (err) {
|
|
366
|
-
console.error('Session invalid callback error:', err);
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ========== PROACTIVE TOKEN REFRESH ==========
|
|
372
|
-
// Schedules token refresh before expiry to ensure seamless UX
|
|
373
|
-
|
|
374
|
-
export function startProactiveRefresh() {
|
|
375
|
-
const { enableProactiveRefresh, tokenRefreshBuffer } = getConfig();
|
|
376
|
-
|
|
377
|
-
if (!enableProactiveRefresh) {
|
|
378
|
-
console.log('โธ๏ธ Proactive refresh disabled by config');
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Clear any existing timer
|
|
383
|
-
stopProactiveRefresh();
|
|
384
|
-
|
|
385
|
-
const token = getToken();
|
|
386
|
-
if (!token) {
|
|
387
|
-
console.log('โธ๏ธ No token, skipping proactive refresh setup');
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const { getTimeUntilExpiry } = require('./token');
|
|
392
|
-
const timeUntilExpiry = getTimeUntilExpiry(token);
|
|
393
|
-
|
|
394
|
-
if (timeUntilExpiry <= 0) {
|
|
395
|
-
console.log('โ ๏ธ Token already expired, attempting immediate refresh');
|
|
396
|
-
refreshToken().catch(err => {
|
|
397
|
-
console.error('โ Immediate refresh failed:', err);
|
|
398
|
-
notifySessionInvalid('token_expired');
|
|
399
|
-
});
|
|
400
|
-
return null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Schedule refresh for (expiry - buffer) seconds from now
|
|
404
|
-
const refreshIn = Math.max(0, (timeUntilExpiry - tokenRefreshBuffer)) * 1000;
|
|
405
|
-
|
|
406
|
-
console.log(`๐ Scheduling proactive refresh in ${Math.round(refreshIn / 1000)}s (token expires in ${timeUntilExpiry}s)`);
|
|
407
|
-
|
|
408
|
-
proactiveRefreshTimer = setTimeout(async () => {
|
|
409
|
-
try {
|
|
410
|
-
console.log('๐ Proactive token refresh triggered');
|
|
411
|
-
await refreshToken();
|
|
412
|
-
console.log('โ
Proactive refresh successful, scheduling next refresh');
|
|
413
|
-
// Schedule next refresh after successful refresh
|
|
414
|
-
startProactiveRefresh();
|
|
415
|
-
} catch (err) {
|
|
416
|
-
console.error('โ Proactive refresh failed:', err);
|
|
417
|
-
|
|
418
|
-
// Check if this is a permanent failure (token revoked, invalid, etc.)
|
|
419
|
-
const errorMessage = err.message?.toLowerCase() || '';
|
|
420
|
-
const isPermanentFailure =
|
|
421
|
-
errorMessage.includes('401') ||
|
|
422
|
-
errorMessage.includes('revoked') ||
|
|
423
|
-
errorMessage.includes('invalid') ||
|
|
424
|
-
errorMessage.includes('expired') ||
|
|
425
|
-
errorMessage.includes('unauthorized');
|
|
426
|
-
|
|
427
|
-
if (isPermanentFailure) {
|
|
428
|
-
console.log('๐จ Token permanently invalid, triggering session expiry');
|
|
429
|
-
notifySessionInvalid('refresh_token_revoked');
|
|
430
|
-
} else {
|
|
431
|
-
// Temporary failure (network issue), try again in 30 seconds
|
|
432
|
-
proactiveRefreshTimer = setTimeout(() => startProactiveRefresh(), 30000);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}, refreshIn);
|
|
436
|
-
|
|
437
|
-
return proactiveRefreshTimer;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
export function stopProactiveRefresh() {
|
|
441
|
-
if (proactiveRefreshTimer) {
|
|
442
|
-
clearTimeout(proactiveRefreshTimer);
|
|
443
|
-
proactiveRefreshTimer = null;
|
|
444
|
-
console.log('โน๏ธ Proactive refresh stopped');
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ========== PERIODIC SESSION VALIDATION ==========
|
|
449
|
-
// Validates with server that session still exists in Keycloak
|
|
450
|
-
// Catches session deletions from Keycloak Admin UI
|
|
451
|
-
|
|
452
|
-
export function startSessionMonitor(onInvalid) {
|
|
453
|
-
const { enableSessionValidation, sessionValidationInterval, validateOnVisibility } = getConfig();
|
|
454
|
-
|
|
455
|
-
if (!enableSessionValidation) {
|
|
456
|
-
console.log('โธ๏ธ Session validation disabled by config');
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Register callback if provided
|
|
461
|
-
if (onInvalid && typeof onInvalid === 'function') {
|
|
462
|
-
sessionInvalidCallbacks.add(onInvalid);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Clear any existing timer
|
|
466
|
-
stopSessionMonitor();
|
|
467
|
-
|
|
468
|
-
const token = getToken();
|
|
469
|
-
if (!token) {
|
|
470
|
-
console.log('โธ๏ธ No token, skipping session monitor setup');
|
|
471
|
-
return null;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
console.log(`๐๏ธ Starting session monitor (interval: ${sessionValidationInterval / 1000}s)`);
|
|
475
|
-
|
|
476
|
-
// Periodic validation
|
|
477
|
-
sessionValidationTimer = setInterval(async () => {
|
|
478
|
-
try {
|
|
479
|
-
const currentToken = getToken();
|
|
480
|
-
if (!currentToken) {
|
|
481
|
-
console.log('โธ๏ธ No token, stopping session validation');
|
|
482
|
-
stopSessionMonitor();
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
console.log('๐ Validating session...');
|
|
487
|
-
const isValid = await validateCurrentSession();
|
|
488
|
-
|
|
489
|
-
if (!isValid) {
|
|
490
|
-
console.log('โ Session no longer valid on server');
|
|
491
|
-
stopSessionMonitor();
|
|
492
|
-
stopProactiveRefresh();
|
|
493
|
-
clearToken();
|
|
494
|
-
clearRefreshToken();
|
|
495
|
-
notifySessionInvalid('session_deleted');
|
|
496
|
-
} else {
|
|
497
|
-
console.log('โ
Session still valid');
|
|
498
|
-
}
|
|
499
|
-
} catch (error) {
|
|
500
|
-
console.warn('โ ๏ธ Session validation check failed:', error.message);
|
|
501
|
-
// Don't invalidate on network errors - wait for next check
|
|
502
|
-
}
|
|
503
|
-
}, sessionValidationInterval);
|
|
504
|
-
|
|
505
|
-
// Visibility-based validation (when tab becomes visible again)
|
|
506
|
-
if (validateOnVisibility && typeof document !== 'undefined') {
|
|
507
|
-
visibilityHandler = async () => {
|
|
508
|
-
if (document.visibilityState === 'visible') {
|
|
509
|
-
const currentToken = getToken();
|
|
510
|
-
if (!currentToken) return;
|
|
511
|
-
|
|
512
|
-
console.log('๐๏ธ Tab visible - validating session');
|
|
513
|
-
try {
|
|
514
|
-
const isValid = await validateCurrentSession();
|
|
515
|
-
if (!isValid) {
|
|
516
|
-
console.log('โ Session expired while tab was hidden');
|
|
517
|
-
stopSessionMonitor();
|
|
518
|
-
stopProactiveRefresh();
|
|
519
|
-
clearToken();
|
|
520
|
-
clearRefreshToken();
|
|
521
|
-
notifySessionInvalid('session_deleted_while_hidden');
|
|
522
|
-
}
|
|
523
|
-
} catch (error) {
|
|
524
|
-
console.warn('โ ๏ธ Visibility check failed:', error.message);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
document.addEventListener('visibilitychange', visibilityHandler);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return sessionValidationTimer;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export function stopSessionMonitor() {
|
|
535
|
-
if (sessionValidationTimer) {
|
|
536
|
-
clearInterval(sessionValidationTimer);
|
|
537
|
-
sessionValidationTimer = null;
|
|
538
|
-
console.log('โน๏ธ Session monitor stopped');
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (visibilityHandler && typeof document !== 'undefined') {
|
|
542
|
-
document.removeEventListener('visibilitychange', visibilityHandler);
|
|
543
|
-
visibilityHandler = null;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// ========== COMBINED SESSION SECURITY ==========
|
|
548
|
-
// Start both proactive refresh and session monitoring
|
|
549
|
-
|
|
550
|
-
export function startSessionSecurity(onSessionInvalidCallback) {
|
|
551
|
-
console.log('๐ Starting session security (proactive refresh + session monitoring)');
|
|
552
|
-
|
|
553
|
-
startProactiveRefresh();
|
|
554
|
-
startSessionMonitor(onSessionInvalidCallback);
|
|
555
|
-
|
|
556
|
-
return {
|
|
557
|
-
stopAll: () => {
|
|
558
|
-
stopProactiveRefresh();
|
|
559
|
-
stopSessionMonitor();
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
export function stopSessionSecurity() {
|
|
565
|
-
stopProactiveRefresh();
|
|
566
|
-
stopSessionMonitor();
|
|
567
|
-
sessionInvalidCallbacks.clear();
|
|
568
|
-
console.log('๐ Session security stopped');
|
|
569
|
-
}
|
|
570
|
-
|
package/index.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// auth-client/index.js
|
|
2
|
-
import { setConfig, getConfig, isRouterMode } from './config';
|
|
3
|
-
import {
|
|
4
|
-
login,
|
|
5
|
-
logout,
|
|
6
|
-
handleCallback,
|
|
7
|
-
refreshToken,
|
|
8
|
-
resetCallbackState,
|
|
9
|
-
validateCurrentSession,
|
|
10
|
-
// Session Security Functions
|
|
11
|
-
startProactiveRefresh,
|
|
12
|
-
stopProactiveRefresh,
|
|
13
|
-
startSessionMonitor,
|
|
14
|
-
stopSessionMonitor,
|
|
15
|
-
startSessionSecurity,
|
|
16
|
-
stopSessionSecurity,
|
|
17
|
-
onSessionInvalid
|
|
18
|
-
} from './core';
|
|
19
|
-
import {
|
|
20
|
-
getToken,
|
|
21
|
-
setToken,
|
|
22
|
-
clearToken,
|
|
23
|
-
setRefreshToken,
|
|
24
|
-
getRefreshToken,
|
|
25
|
-
clearRefreshToken,
|
|
26
|
-
addTokenListener,
|
|
27
|
-
removeTokenListener,
|
|
28
|
-
getListenerCount,
|
|
29
|
-
// Token Expiry Utilities
|
|
30
|
-
getTokenExpiryTime,
|
|
31
|
-
getTimeUntilExpiry,
|
|
32
|
-
willExpireSoon
|
|
33
|
-
} from './token';
|
|
34
|
-
import api from './api';
|
|
35
|
-
import { decodeToken, isTokenExpired, isAuthenticated } from './utils/jwt';
|
|
36
|
-
|
|
37
|
-
export const auth = {
|
|
38
|
-
// ๐ง Config
|
|
39
|
-
setConfig,
|
|
40
|
-
getConfig,
|
|
41
|
-
isRouterMode,
|
|
42
|
-
|
|
43
|
-
// ๐ Core flows
|
|
44
|
-
login,
|
|
45
|
-
logout,
|
|
46
|
-
handleCallback,
|
|
47
|
-
refreshToken,
|
|
48
|
-
resetCallbackState,
|
|
49
|
-
validateCurrentSession,
|
|
50
|
-
|
|
51
|
-
// ๐ Token management
|
|
52
|
-
getToken,
|
|
53
|
-
setToken,
|
|
54
|
-
clearToken,
|
|
55
|
-
setRefreshToken, // โ
Refresh token for HTTP dev
|
|
56
|
-
getRefreshToken,
|
|
57
|
-
clearRefreshToken,
|
|
58
|
-
addTokenListener, // โ
Export new functions
|
|
59
|
-
removeTokenListener,
|
|
60
|
-
getListenerCount, // โ
Debug function
|
|
61
|
-
|
|
62
|
-
// ๐ Authenticated API client
|
|
63
|
-
api,
|
|
64
|
-
|
|
65
|
-
// ๐งช Utilities
|
|
66
|
-
decodeToken,
|
|
67
|
-
isTokenExpired,
|
|
68
|
-
isAuthenticated,
|
|
69
|
-
|
|
70
|
-
// โฑ๏ธ Token Expiry Utilities (NEW)
|
|
71
|
-
getTokenExpiryTime, // Get token expiry as Date object
|
|
72
|
-
getTimeUntilExpiry, // Get seconds until token expires
|
|
73
|
-
willExpireSoon, // Check if token expires within N seconds
|
|
74
|
-
|
|
75
|
-
// ๐ Session Security (NEW - Short-lived tokens + Periodic validation)
|
|
76
|
-
startProactiveRefresh, // Start proactive token refresh before expiry
|
|
77
|
-
stopProactiveRefresh, // Stop proactive refresh
|
|
78
|
-
startSessionMonitor, // Start periodic session validation
|
|
79
|
-
stopSessionMonitor, // Stop session validation
|
|
80
|
-
startSessionSecurity, // Start both proactive refresh AND session monitoring
|
|
81
|
-
stopSessionSecurity, // Stop all session security
|
|
82
|
-
onSessionInvalid, // Register callback for session invalidation
|
|
83
|
-
|
|
84
|
-
// ๐ Legacy auto-refresh (DEPRECATED - use startSessionSecurity instead)
|
|
85
|
-
startTokenRefresh: () => {
|
|
86
|
-
console.warn('โ ๏ธ startTokenRefresh is deprecated. Use startSessionSecurity() instead for better session management.');
|
|
87
|
-
const interval = setInterval(async () => {
|
|
88
|
-
const token = getToken();
|
|
89
|
-
if (token && isTokenExpired(token, 300)) {
|
|
90
|
-
try {
|
|
91
|
-
await refreshToken();
|
|
92
|
-
console.log('๐ Auto-refresh successful');
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error('Auto-refresh failed:', err);
|
|
95
|
-
clearInterval(interval);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}, 60000);
|
|
99
|
-
return interval;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export { AuthProvider } from './react/AuthProvider';
|
|
104
|
-
export { useAuth } from './react/useAuth';
|
|
105
|
-
export { useSessionMonitor } from './react/useSessionMonitor';
|
|
106
|
-
|