win-portal-auth-sdk 1.3.0 → 1.4.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 +80 -7
- package/dist/client/auth-client.d.ts +97 -2
- package/dist/client/auth-client.d.ts.map +1 -1
- package/dist/client/auth-client.js +276 -30
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -2
- package/dist/nestjs/decorators/current-token.decorator.d.ts +29 -0
- package/dist/nestjs/decorators/current-token.decorator.d.ts.map +1 -0
- package/dist/nestjs/decorators/current-token.decorator.js +36 -0
- package/dist/nestjs/decorators/current-user.decorator.d.ts +26 -0
- package/dist/nestjs/decorators/current-user.decorator.d.ts.map +1 -0
- package/dist/nestjs/decorators/current-user.decorator.js +33 -0
- package/dist/nestjs/decorators/index.d.ts +9 -0
- package/dist/nestjs/decorators/index.d.ts.map +1 -0
- package/dist/nestjs/decorators/index.js +15 -0
- package/dist/nestjs/decorators/public.decorator.d.ts +31 -0
- package/dist/nestjs/decorators/public.decorator.d.ts.map +1 -0
- package/dist/nestjs/decorators/public.decorator.js +36 -0
- package/dist/nestjs/guards/index.d.ts +7 -0
- package/dist/nestjs/guards/index.d.ts.map +1 -0
- package/dist/nestjs/guards/index.js +11 -0
- package/dist/nestjs/guards/oauth-auth.guard.d.ts +139 -0
- package/dist/nestjs/guards/oauth-auth.guard.d.ts.map +1 -0
- package/dist/nestjs/guards/oauth-auth.guard.js +257 -0
- package/dist/nestjs/index.d.ts +28 -0
- package/dist/nestjs/index.d.ts.map +1 -0
- package/dist/nestjs/index.js +47 -0
- package/dist/nestjs/middleware/index.d.ts +7 -0
- package/dist/nestjs/middleware/index.d.ts.map +1 -0
- package/dist/nestjs/middleware/index.js +11 -0
- package/dist/nestjs/middleware/request-context.middleware.d.ts +62 -0
- package/dist/nestjs/middleware/request-context.middleware.d.ts.map +1 -0
- package/dist/nestjs/middleware/request-context.middleware.js +122 -0
- package/dist/nestjs/types/request-context.types.d.ts +69 -0
- package/dist/nestjs/types/request-context.types.d.ts.map +1 -0
- package/dist/nestjs/types/request-context.types.js +33 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +23 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +49 -0
- package/package.json +41 -3
|
@@ -14,6 +14,17 @@ exports.AuthClient = void 0;
|
|
|
14
14
|
const axios_1 = __importDefault(require("axios"));
|
|
15
15
|
const api_1 = require("./api");
|
|
16
16
|
const token_utils_1 = require("../utils/token-utils");
|
|
17
|
+
const logger_1 = require("../utils/logger");
|
|
18
|
+
// Constants - Default values
|
|
19
|
+
const DEFAULT_INACTIVITY_TIMEOUT_SECONDS = 30 * 60; // 30 minutes
|
|
20
|
+
const DEFAULT_WARNING_SECONDS = 5 * 60; // 5 minutes
|
|
21
|
+
const DEFAULT_SESSION_CHECK_INTERVAL_SECONDS = 30; // 30 seconds
|
|
22
|
+
// Internal constants - Rate limiting (ไม่ควรให้ customize)
|
|
23
|
+
const REFRESH_COOLDOWN_MS = 30 * 1000; // 30 seconds - rate limiting for refresh
|
|
24
|
+
const WARNING_COOLDOWN_MS = 60 * 1000; // 1 minute - rate limiting for warnings
|
|
25
|
+
// Internal constants - Performance tuning (สามารถ customize ได้ผ่าน advanced config)
|
|
26
|
+
const DEFAULT_ACTIVITY_THROTTLE_MS = 1000; // 1 second - throttle activity handler
|
|
27
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 5000; // 5 seconds - refresh timeout
|
|
17
28
|
class AuthClient {
|
|
18
29
|
constructor(config) {
|
|
19
30
|
this.token = null;
|
|
@@ -25,21 +36,38 @@ class AuthClient {
|
|
|
25
36
|
this.automaticRefreshEnabled = null;
|
|
26
37
|
this.lastRefreshAttempt = 0;
|
|
27
38
|
this.refreshPromise = null;
|
|
28
|
-
this.REFRESH_COOLDOWN_MS = 30 * 1000; // 30 seconds cooldown
|
|
29
39
|
// Session expiration monitoring
|
|
30
40
|
this.sessionExpirationCallbacks = null;
|
|
31
41
|
this.sessionManagementConfig = null;
|
|
32
42
|
this.sessionManagementConfigPromise = null;
|
|
33
43
|
this.sessionExpirationCheckInterval = null;
|
|
34
|
-
this.sessionExpirationCheckIntervalSeconds =
|
|
44
|
+
this.sessionExpirationCheckIntervalSeconds = DEFAULT_SESSION_CHECK_INTERVAL_SECONDS;
|
|
35
45
|
this.lastWarningTime = 0;
|
|
36
|
-
|
|
46
|
+
// Inactivity detection
|
|
47
|
+
this.inactivityCallbacks = null;
|
|
48
|
+
this.inactivityTimeout = null;
|
|
49
|
+
this.inactivityWarningTimeout = null;
|
|
50
|
+
this.lastActivityTime = 0;
|
|
51
|
+
this.inactivityDetectionEnabled = false;
|
|
52
|
+
this.inactivityTimeoutSeconds = 0;
|
|
53
|
+
this.inactivityWarningSeconds = 0;
|
|
54
|
+
this.activityEvents = [];
|
|
55
|
+
this.activityHandler = null;
|
|
56
|
+
this.activityThrottleTimeout = null;
|
|
57
|
+
this.activityThrottleMs = DEFAULT_ACTIVITY_THROTTLE_MS;
|
|
58
|
+
this.refreshTimeoutMs = DEFAULT_REFRESH_TIMEOUT_MS;
|
|
37
59
|
this.apiKey = config.apiKey;
|
|
38
60
|
this.apiKeyHeader = config.apiKeyHeader || 'X-API-Key';
|
|
39
|
-
|
|
61
|
+
// Apply advanced config if provided
|
|
62
|
+
if (config.advanced) {
|
|
63
|
+
this.activityThrottleMs = config.advanced.activityThrottleMs ?? DEFAULT_ACTIVITY_THROTTLE_MS;
|
|
64
|
+
this.refreshTimeoutMs = config.advanced.refreshTimeoutMs ?? DEFAULT_REFRESH_TIMEOUT_MS;
|
|
65
|
+
}
|
|
66
|
+
logger_1.logger.debug('Initializing with config:', {
|
|
40
67
|
baseURL: config.baseURL,
|
|
41
68
|
timeout: config.timeout,
|
|
42
69
|
apiKeyHeader: this.apiKeyHeader,
|
|
70
|
+
advanced: config.advanced,
|
|
43
71
|
});
|
|
44
72
|
this.client = axios_1.default.create({
|
|
45
73
|
baseURL: config.baseURL,
|
|
@@ -67,29 +95,28 @@ class AuthClient {
|
|
|
67
95
|
const isCritical = remainingMinutes !== null && remainingMinutes <= 1;
|
|
68
96
|
const now = Date.now();
|
|
69
97
|
const timeSinceLastRefresh = now - this.lastRefreshAttempt;
|
|
70
|
-
if (timeSinceLastRefresh >= this.
|
|
71
|
-
|
|
72
|
-
console.log(`[AuthClient] Token near expiration (${remainingMinutes} min remaining), refreshing...`);
|
|
98
|
+
if (timeSinceLastRefresh >= REFRESH_COOLDOWN_MS && !this.refreshPromise) {
|
|
99
|
+
logger_1.logger.debug(`Token near expiration (${remainingMinutes} min remaining), refreshing...`);
|
|
73
100
|
this.lastRefreshAttempt = now;
|
|
74
101
|
if (isCritical) {
|
|
75
102
|
// Critical: await refresh
|
|
76
103
|
try {
|
|
77
104
|
const newToken = await Promise.race([
|
|
78
105
|
this.performRefresh(),
|
|
79
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Refresh timeout')),
|
|
106
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Refresh timeout')), this.refreshTimeoutMs)),
|
|
80
107
|
]);
|
|
81
108
|
this.token = newToken;
|
|
82
109
|
requestConfig.headers['Authorization'] = `Bearer ${newToken}`;
|
|
83
|
-
|
|
110
|
+
logger_1.logger.info('Token refreshed successfully (critical)');
|
|
84
111
|
}
|
|
85
112
|
catch (error) {
|
|
86
|
-
|
|
113
|
+
logger_1.logger.error('Critical token refresh failed:', error);
|
|
87
114
|
}
|
|
88
115
|
}
|
|
89
116
|
else {
|
|
90
117
|
// Background refresh
|
|
91
118
|
this.performRefresh().catch((error) => {
|
|
92
|
-
|
|
119
|
+
logger_1.logger.error('Background token refresh failed:', error);
|
|
93
120
|
});
|
|
94
121
|
}
|
|
95
122
|
}
|
|
@@ -107,7 +134,7 @@ class AuthClient {
|
|
|
107
134
|
// Handle 401 errors - try refresh token before failing
|
|
108
135
|
if (error.response?.status === 401 && originalRequest && this.refreshTokenCallbacks) {
|
|
109
136
|
if (originalRequest._retry) {
|
|
110
|
-
|
|
137
|
+
logger_1.logger.warn('401 Unauthorized after retry - refresh failed');
|
|
111
138
|
if (this.refreshTokenCallbacks.onRefreshFailure) {
|
|
112
139
|
await this.refreshTokenCallbacks.onRefreshFailure();
|
|
113
140
|
}
|
|
@@ -115,7 +142,7 @@ class AuthClient {
|
|
|
115
142
|
}
|
|
116
143
|
// Skip refresh for refresh endpoint itself
|
|
117
144
|
if (originalRequest.url?.includes('/auth/refresh') || originalRequest.url?.includes('/auth/refresh-token')) {
|
|
118
|
-
|
|
145
|
+
logger_1.logger.error('Refresh endpoint failed - session expired');
|
|
119
146
|
if (this.refreshTokenCallbacks.onRefreshFailure) {
|
|
120
147
|
await this.refreshTokenCallbacks.onRefreshFailure();
|
|
121
148
|
}
|
|
@@ -124,15 +151,15 @@ class AuthClient {
|
|
|
124
151
|
// Try to refresh token
|
|
125
152
|
try {
|
|
126
153
|
originalRequest._retry = true;
|
|
127
|
-
|
|
154
|
+
logger_1.logger.debug('401 detected, attempting token refresh...');
|
|
128
155
|
const newToken = await this.performRefresh();
|
|
129
156
|
originalRequest.headers = originalRequest.headers || {};
|
|
130
157
|
originalRequest.headers.Authorization = `Bearer ${newToken}`;
|
|
131
|
-
|
|
158
|
+
logger_1.logger.debug('Token refreshed, retrying original request...');
|
|
132
159
|
return this.client(originalRequest);
|
|
133
160
|
}
|
|
134
161
|
catch (refreshError) {
|
|
135
|
-
|
|
162
|
+
logger_1.logger.error('Token refresh failed:', refreshError);
|
|
136
163
|
if (this.refreshTokenCallbacks.onRefreshFailure) {
|
|
137
164
|
await this.refreshTokenCallbacks.onRefreshFailure();
|
|
138
165
|
}
|
|
@@ -141,10 +168,10 @@ class AuthClient {
|
|
|
141
168
|
}
|
|
142
169
|
// Handle other errors
|
|
143
170
|
if (error.response?.status === 401) {
|
|
144
|
-
|
|
171
|
+
logger_1.logger.error('API Key authentication failed');
|
|
145
172
|
}
|
|
146
173
|
else if (error.response?.status === 429) {
|
|
147
|
-
|
|
174
|
+
logger_1.logger.error('Rate limit exceeded');
|
|
148
175
|
}
|
|
149
176
|
return Promise.reject(error);
|
|
150
177
|
});
|
|
@@ -170,7 +197,7 @@ class AuthClient {
|
|
|
170
197
|
* ```
|
|
171
198
|
*/
|
|
172
199
|
initializeOAuth(config) {
|
|
173
|
-
|
|
200
|
+
logger_1.logger.debug('Initializing OAuth with:', {
|
|
174
201
|
clientId: config.clientId,
|
|
175
202
|
redirectUri: config.redirectUri,
|
|
176
203
|
scope: config.scope,
|
|
@@ -244,7 +271,7 @@ class AuthClient {
|
|
|
244
271
|
setToken(token, type = 'jwt') {
|
|
245
272
|
this.token = token;
|
|
246
273
|
this.authType = type;
|
|
247
|
-
|
|
274
|
+
logger_1.logger.debug(`Token set with type: ${type}`);
|
|
248
275
|
// Reset warning time when token changes
|
|
249
276
|
this.lastWarningTime = 0;
|
|
250
277
|
// If session expiration monitoring is enabled, check immediately
|
|
@@ -265,7 +292,7 @@ class AuthClient {
|
|
|
265
292
|
*/
|
|
266
293
|
setAuthType(type) {
|
|
267
294
|
this.authType = type;
|
|
268
|
-
|
|
295
|
+
logger_1.logger.debug(`Auth type changed to: ${type}`);
|
|
269
296
|
}
|
|
270
297
|
/**
|
|
271
298
|
* Get current token (masked)
|
|
@@ -379,10 +406,10 @@ class AuthClient {
|
|
|
379
406
|
const config = await this.systemConfig.getSecurityJwtConfig();
|
|
380
407
|
this.refreshThresholdMinutes = config.refresh_threshold_minutes;
|
|
381
408
|
this.automaticRefreshEnabled = config.automatic_refresh ?? true;
|
|
382
|
-
|
|
409
|
+
logger_1.logger.debug(`JWT config loaded: refresh_threshold_minutes=${this.refreshThresholdMinutes} minutes, automatic_refresh=${this.automaticRefreshEnabled}`);
|
|
383
410
|
}
|
|
384
411
|
catch (error) {
|
|
385
|
-
|
|
412
|
+
logger_1.logger.error('Failed to load JWT config:', error);
|
|
386
413
|
// Reset to allow retry
|
|
387
414
|
this.jwtConfigPromise = null;
|
|
388
415
|
throw error;
|
|
@@ -429,7 +456,7 @@ class AuthClient {
|
|
|
429
456
|
if (session.refresh_token) {
|
|
430
457
|
await this.refreshTokenCallbacks.setRefreshToken(session.refresh_token);
|
|
431
458
|
}
|
|
432
|
-
|
|
459
|
+
logger_1.logger.debug('Token refreshed successfully');
|
|
433
460
|
return newAccessToken;
|
|
434
461
|
}
|
|
435
462
|
catch (error) {
|
|
@@ -442,11 +469,11 @@ class AuthClient {
|
|
|
442
469
|
if (tokens.refresh_token) {
|
|
443
470
|
await this.refreshTokenCallbacks.setRefreshToken(tokens.refresh_token);
|
|
444
471
|
}
|
|
445
|
-
|
|
472
|
+
logger_1.logger.debug('Token refreshed successfully (fallback)');
|
|
446
473
|
return newAccessToken;
|
|
447
474
|
}
|
|
448
475
|
catch (fallbackError) {
|
|
449
|
-
|
|
476
|
+
logger_1.logger.error('Fallback refresh also failed:', fallbackError);
|
|
450
477
|
throw fallbackError;
|
|
451
478
|
}
|
|
452
479
|
}
|
|
@@ -487,7 +514,7 @@ class AuthClient {
|
|
|
487
514
|
*/
|
|
488
515
|
async enableSessionExpirationMonitoring(options) {
|
|
489
516
|
this.sessionExpirationCallbacks = options.callbacks;
|
|
490
|
-
this.sessionExpirationCheckIntervalSeconds = options.checkIntervalSeconds ??
|
|
517
|
+
this.sessionExpirationCheckIntervalSeconds = options.checkIntervalSeconds ?? DEFAULT_SESSION_CHECK_INTERVAL_SECONDS;
|
|
491
518
|
// Load session management config
|
|
492
519
|
await this.loadSessionManagementConfig();
|
|
493
520
|
// Start monitoring
|
|
@@ -519,7 +546,7 @@ class AuthClient {
|
|
|
519
546
|
try {
|
|
520
547
|
const config = await this.systemConfig.getSessionManagement();
|
|
521
548
|
this.sessionManagementConfig = config;
|
|
522
|
-
|
|
549
|
+
logger_1.logger.debug('Session management config loaded:', {
|
|
523
550
|
inactivityEnabled: config.inactivity.enabled,
|
|
524
551
|
inactivityTimeout: `${config.inactivity.timeout_duration} ${config.inactivity.timeout_unit}`,
|
|
525
552
|
inactivityWarningMinutes: config.inactivity.warning_minutes,
|
|
@@ -528,7 +555,7 @@ class AuthClient {
|
|
|
528
555
|
});
|
|
529
556
|
}
|
|
530
557
|
catch (error) {
|
|
531
|
-
|
|
558
|
+
logger_1.logger.error('Failed to load session management config:', error);
|
|
532
559
|
// Reset to allow retry
|
|
533
560
|
this.sessionManagementConfigPromise = null;
|
|
534
561
|
throw error;
|
|
@@ -628,7 +655,7 @@ class AuthClient {
|
|
|
628
655
|
// Check cooldown to avoid spam
|
|
629
656
|
const now = Date.now();
|
|
630
657
|
const timeSinceLastWarning = now - this.lastWarningTime;
|
|
631
|
-
if (timeSinceLastWarning >=
|
|
658
|
+
if (timeSinceLastWarning >= WARNING_COOLDOWN_MS) {
|
|
632
659
|
this.lastWarningTime = now;
|
|
633
660
|
this.sessionExpirationCallbacks.onSessionExpiring(remainingMinutes, expirationType);
|
|
634
661
|
}
|
|
@@ -641,5 +668,224 @@ class AuthClient {
|
|
|
641
668
|
this.sessionManagementConfig = null;
|
|
642
669
|
await this.loadSessionManagementConfig();
|
|
643
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Enable inactivity detection
|
|
673
|
+
* จับ user activity (mouse/keyboard/touch/scroll) และ trigger callback เมื่อ inactivity timeout
|
|
674
|
+
*
|
|
675
|
+
* @param options - Options for inactivity detection
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* await authClient.enableInactivityDetection({
|
|
680
|
+
* callbacks: {
|
|
681
|
+
* onInactivityWarning: (remainingSeconds) => {
|
|
682
|
+
* console.log(`Inactivity warning: ${remainingSeconds} seconds remaining`);
|
|
683
|
+
* // แสดง notification หรือ dialog
|
|
684
|
+
* },
|
|
685
|
+
* onInactivityTimeout: () => {
|
|
686
|
+
* console.log('Inactivity timeout');
|
|
687
|
+
* // Redirect to login หรือ logout
|
|
688
|
+
* authClient.clearToken();
|
|
689
|
+
* window.location.href = '/login';
|
|
690
|
+
* }
|
|
691
|
+
* },
|
|
692
|
+
* timeoutSeconds: 1800, // 30 minutes (optional, default: จาก session management config)
|
|
693
|
+
* warningSeconds: 300, // 5 minutes warning (optional, default: จาก session management config)
|
|
694
|
+
* events: ['mousemove', 'keydown', 'touchstart', 'scroll'] // (optional, default: ทั้งหมด)
|
|
695
|
+
* });
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
async enableInactivityDetection(options) {
|
|
699
|
+
// Disable existing detection if any
|
|
700
|
+
this.disableInactivityDetection();
|
|
701
|
+
this.inactivityCallbacks = options.callbacks;
|
|
702
|
+
// Load session management config if not loaded
|
|
703
|
+
if (!this.sessionManagementConfig) {
|
|
704
|
+
await this.loadSessionManagementConfig();
|
|
705
|
+
}
|
|
706
|
+
// Determine timeout and warning seconds
|
|
707
|
+
if (options.timeoutSeconds !== undefined) {
|
|
708
|
+
this.inactivityTimeoutSeconds = options.timeoutSeconds;
|
|
709
|
+
}
|
|
710
|
+
else if (this.sessionManagementConfig?.inactivity.enabled) {
|
|
711
|
+
// Use session management config
|
|
712
|
+
this.inactivityTimeoutSeconds =
|
|
713
|
+
(0, token_utils_1.convertDurationToMinutes)(this.sessionManagementConfig.inactivity.timeout_duration, this.sessionManagementConfig.inactivity.timeout_unit) * 60; // Convert to seconds
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
// Default: 30 minutes
|
|
717
|
+
this.inactivityTimeoutSeconds = DEFAULT_INACTIVITY_TIMEOUT_SECONDS;
|
|
718
|
+
}
|
|
719
|
+
if (options.warningSeconds !== undefined) {
|
|
720
|
+
this.inactivityWarningSeconds = options.warningSeconds;
|
|
721
|
+
}
|
|
722
|
+
else if (this.sessionManagementConfig?.inactivity.warning_minutes) {
|
|
723
|
+
// Use session management config (convert minutes to seconds)
|
|
724
|
+
this.inactivityWarningSeconds = this.sessionManagementConfig.inactivity.warning_minutes * 60;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// Default: 5 minutes
|
|
728
|
+
this.inactivityWarningSeconds = DEFAULT_WARNING_SECONDS;
|
|
729
|
+
}
|
|
730
|
+
// Determine events to listen
|
|
731
|
+
this.activityEvents = options.events || ['mousemove', 'keydown', 'touchstart', 'scroll'];
|
|
732
|
+
// Initialize last activity time
|
|
733
|
+
this.lastActivityTime = Date.now();
|
|
734
|
+
// Enable detection
|
|
735
|
+
this.inactivityDetectionEnabled = true;
|
|
736
|
+
// Setup event listeners
|
|
737
|
+
this.setupActivityListeners();
|
|
738
|
+
// Start timer
|
|
739
|
+
this.resetInactivityTimer();
|
|
740
|
+
logger_1.logger.debug('Inactivity detection enabled:', {
|
|
741
|
+
timeoutSeconds: this.inactivityTimeoutSeconds,
|
|
742
|
+
warningSeconds: this.inactivityWarningSeconds,
|
|
743
|
+
events: this.activityEvents,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Disable inactivity detection
|
|
748
|
+
*/
|
|
749
|
+
disableInactivityDetection() {
|
|
750
|
+
this.inactivityDetectionEnabled = false;
|
|
751
|
+
// Clear throttle timeout
|
|
752
|
+
if (this.activityThrottleTimeout) {
|
|
753
|
+
clearTimeout(this.activityThrottleTimeout);
|
|
754
|
+
this.activityThrottleTimeout = null;
|
|
755
|
+
}
|
|
756
|
+
// Clear timeouts
|
|
757
|
+
this.clearInactivityTimeouts();
|
|
758
|
+
// Remove event listeners
|
|
759
|
+
this.removeActivityListeners();
|
|
760
|
+
// Reset state
|
|
761
|
+
this.inactivityCallbacks = null;
|
|
762
|
+
this.lastActivityTime = 0;
|
|
763
|
+
this.inactivityTimeoutSeconds = 0;
|
|
764
|
+
this.inactivityWarningSeconds = 0;
|
|
765
|
+
this.activityEvents = [];
|
|
766
|
+
logger_1.logger.debug('Inactivity detection disabled');
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Handle user activity - reset inactivity timer with throttle
|
|
770
|
+
*/
|
|
771
|
+
handleUserActivity() {
|
|
772
|
+
if (!this.inactivityDetectionEnabled) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
// Throttle: reset timer สูงสุดทุก 1 วินาที เพื่อลด CPU usage
|
|
776
|
+
if (this.activityThrottleTimeout) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
this.lastActivityTime = Date.now();
|
|
780
|
+
this.resetInactivityTimer();
|
|
781
|
+
this.activityThrottleTimeout = setTimeout(() => {
|
|
782
|
+
this.activityThrottleTimeout = null;
|
|
783
|
+
}, this.activityThrottleMs);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Setup activity event listeners
|
|
787
|
+
*/
|
|
788
|
+
setupActivityListeners() {
|
|
789
|
+
if (typeof window === 'undefined') {
|
|
790
|
+
logger_1.logger.warn('Cannot setup activity listeners: window is undefined (SSR)');
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
// Remove existing listeners if any
|
|
794
|
+
this.removeActivityListeners();
|
|
795
|
+
// Create handler
|
|
796
|
+
const handler = () => {
|
|
797
|
+
this.handleUserActivity();
|
|
798
|
+
};
|
|
799
|
+
this.activityHandler = handler;
|
|
800
|
+
// Setup listeners for each event type
|
|
801
|
+
this.activityEvents.forEach((eventType) => {
|
|
802
|
+
window.addEventListener(eventType, handler, { passive: true });
|
|
803
|
+
});
|
|
804
|
+
logger_1.logger.debug('Activity listeners setup:', this.activityEvents);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Remove activity event listeners
|
|
808
|
+
*/
|
|
809
|
+
removeActivityListeners() {
|
|
810
|
+
if (typeof window === 'undefined' || !this.activityHandler) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const handler = this.activityHandler;
|
|
814
|
+
this.activityEvents.forEach((eventType) => {
|
|
815
|
+
window.removeEventListener(eventType, handler);
|
|
816
|
+
});
|
|
817
|
+
this.activityHandler = null;
|
|
818
|
+
logger_1.logger.debug('Activity listeners removed');
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Clear inactivity timeouts (helper method)
|
|
822
|
+
*/
|
|
823
|
+
clearInactivityTimeouts() {
|
|
824
|
+
if (this.inactivityWarningTimeout) {
|
|
825
|
+
clearTimeout(this.inactivityWarningTimeout);
|
|
826
|
+
this.inactivityWarningTimeout = null;
|
|
827
|
+
}
|
|
828
|
+
if (this.inactivityTimeout) {
|
|
829
|
+
clearTimeout(this.inactivityTimeout);
|
|
830
|
+
this.inactivityTimeout = null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Reset inactivity timer
|
|
835
|
+
*/
|
|
836
|
+
resetInactivityTimer() {
|
|
837
|
+
// Clear existing timeouts
|
|
838
|
+
this.clearInactivityTimeouts();
|
|
839
|
+
if (!this.inactivityDetectionEnabled || !this.inactivityCallbacks) {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const now = Date.now();
|
|
843
|
+
this.lastActivityTime = now;
|
|
844
|
+
// Setup warning timeout
|
|
845
|
+
if (this.inactivityWarningSeconds > 0 && this.inactivityCallbacks.onInactivityWarning) {
|
|
846
|
+
const warningTime = this.inactivityTimeoutSeconds - this.inactivityWarningSeconds;
|
|
847
|
+
if (warningTime > 0) {
|
|
848
|
+
const timerStartTime = now; // Capture start time for this timer
|
|
849
|
+
this.inactivityWarningTimeout = setTimeout(() => {
|
|
850
|
+
// Calculate remaining seconds based on when timer was set
|
|
851
|
+
const elapsedSeconds = (Date.now() - timerStartTime) / 1000;
|
|
852
|
+
const remainingSeconds = this.inactivityTimeoutSeconds - elapsedSeconds;
|
|
853
|
+
if (this.inactivityCallbacks?.onInactivityWarning && remainingSeconds > 0) {
|
|
854
|
+
this.inactivityCallbacks.onInactivityWarning(Math.floor(remainingSeconds));
|
|
855
|
+
}
|
|
856
|
+
}, warningTime * 1000);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
// Setup timeout
|
|
860
|
+
this.inactivityTimeout = setTimeout(() => {
|
|
861
|
+
if (this.inactivityCallbacks?.onInactivityTimeout) {
|
|
862
|
+
this.inactivityCallbacks.onInactivityTimeout();
|
|
863
|
+
}
|
|
864
|
+
}, this.inactivityTimeoutSeconds * 1000);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Check inactivity timeout and trigger callbacks if needed
|
|
868
|
+
*/
|
|
869
|
+
checkInactivityTimeout() {
|
|
870
|
+
if (!this.inactivityDetectionEnabled || !this.inactivityCallbacks) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const now = Date.now();
|
|
874
|
+
const inactiveSeconds = (now - this.lastActivityTime) / 1000;
|
|
875
|
+
// Check if timeout reached
|
|
876
|
+
if (inactiveSeconds >= this.inactivityTimeoutSeconds) {
|
|
877
|
+
if (this.inactivityCallbacks.onInactivityTimeout) {
|
|
878
|
+
this.inactivityCallbacks.onInactivityTimeout();
|
|
879
|
+
}
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
// Check if warning should be triggered
|
|
883
|
+
const remainingSeconds = this.inactivityTimeoutSeconds - inactiveSeconds;
|
|
884
|
+
if (remainingSeconds <= this.inactivityWarningSeconds &&
|
|
885
|
+
remainingSeconds > 0 &&
|
|
886
|
+
this.inactivityCallbacks.onInactivityWarning) {
|
|
887
|
+
this.inactivityCallbacks.onInactivityWarning(Math.floor(remainingSeconds));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
644
890
|
}
|
|
645
891
|
exports.AuthClient = AuthClient;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Client exports for frontend applications
|
|
3
3
|
*/
|
|
4
4
|
export { AuthClient } from './auth-client';
|
|
5
|
-
export type { RefreshTokenCallbacks, AutomaticRefreshOptions, SessionExpirationCallbacks, SessionExpirationOptions, } from './auth-client';
|
|
5
|
+
export type { RefreshTokenCallbacks, AutomaticRefreshOptions, SessionExpirationCallbacks, SessionExpirationOptions, InactivityCallbacks, InactivityDetectionOptions, } from './auth-client';
|
|
6
6
|
export * from './api';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,cAAc,OAAO,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Authentication SDK for Win Portal applications
|
|
5
5
|
* Provides HTTP client with automatic API key injection
|
|
6
|
+
*
|
|
7
|
+
* Core exports - ใช้ได้ทุกที่ (Frontend & Backend)
|
|
8
|
+
*
|
|
9
|
+
* สำหรับ Backend-specific features:
|
|
10
|
+
* - Express Middleware: import from 'win-portal-auth-sdk/middleware'
|
|
11
|
+
* - NestJS Components: import from 'win-portal-auth-sdk/nestjs'
|
|
6
12
|
*/
|
|
7
13
|
export * from './types';
|
|
8
14
|
export * from './client';
|
|
9
|
-
export * from './middleware';
|
|
10
15
|
export * from './utils/token-utils';
|
|
16
|
+
export { logger } from './utils/logger';
|
|
11
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,cAAc,SAAS,CAAC;AAGxB,cAAc,UAAU,CAAC;AAGzB,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Authentication SDK for Win Portal applications
|
|
6
6
|
* Provides HTTP client with automatic API key injection
|
|
7
|
+
*
|
|
8
|
+
* Core exports - ใช้ได้ทุกที่ (Frontend & Backend)
|
|
9
|
+
*
|
|
10
|
+
* สำหรับ Backend-specific features:
|
|
11
|
+
* - Express Middleware: import from 'win-portal-auth-sdk/middleware'
|
|
12
|
+
* - NestJS Components: import from 'win-portal-auth-sdk/nestjs'
|
|
7
13
|
*/
|
|
8
14
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
15
|
if (k2 === undefined) k2 = k;
|
|
@@ -20,11 +26,17 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
26
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
27
|
};
|
|
22
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.logger = void 0;
|
|
23
30
|
// Types
|
|
24
31
|
__exportStar(require("./types"), exports);
|
|
25
32
|
// Client (Frontend & Backend)
|
|
26
33
|
__exportStar(require("./client"), exports);
|
|
27
|
-
// Middleware (Backend only - Express & NestJS)
|
|
28
|
-
__exportStar(require("./middleware"), exports);
|
|
29
34
|
// Utils
|
|
30
35
|
__exportStar(require("./utils/token-utils"), exports);
|
|
36
|
+
var logger_1 = require("./utils/logger");
|
|
37
|
+
Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
|
|
38
|
+
// Note: Middleware และ NestJS components ไม่ได้ export จาก root
|
|
39
|
+
// เพื่อป้องกันการ bundle backend code ใน frontend applications
|
|
40
|
+
// ใช้ subpath exports แทน:
|
|
41
|
+
// - 'win-portal-auth-sdk/middleware' สำหรับ Express middleware
|
|
42
|
+
// - 'win-portal-auth-sdk/nestjs' สำหรับ NestJS components
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current Token Decorator
|
|
3
|
+
*
|
|
4
|
+
* Parameter decorator to extract the current authentication token from the request.
|
|
5
|
+
* Requires that a guard has set request.token (e.g., OAuthAuthGuard).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Current Token Decorator
|
|
9
|
+
*
|
|
10
|
+
* Extracts the authentication token from request.token.
|
|
11
|
+
* Returns null if no token is available.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Controller('api')
|
|
16
|
+
* @UseGuards(OAuthAuthGuard)
|
|
17
|
+
* export class ApiController {
|
|
18
|
+
* @Get('token-info')
|
|
19
|
+
* getTokenInfo(@CurrentToken() token: string) {
|
|
20
|
+
* return {
|
|
21
|
+
* token: token.substring(0, 20) + '...',
|
|
22
|
+
* length: token.length,
|
|
23
|
+
* };
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const CurrentToken: any;
|
|
29
|
+
//# sourceMappingURL=current-token.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"current-token.decorator.d.ts","sourceRoot":"","sources":["../../../src/nestjs/decorators/current-token.decorator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,YAAY,KAGvB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Current Token Decorator
|
|
4
|
+
*
|
|
5
|
+
* Parameter decorator to extract the current authentication token from the request.
|
|
6
|
+
* Requires that a guard has set request.token (e.g., OAuthAuthGuard).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CurrentToken = void 0;
|
|
10
|
+
// @ts-expect-error - @nestjs/common is a peer dependency, will be available at runtime in NestJS apps
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
/**
|
|
13
|
+
* Current Token Decorator
|
|
14
|
+
*
|
|
15
|
+
* Extracts the authentication token from request.token.
|
|
16
|
+
* Returns null if no token is available.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* @Controller('api')
|
|
21
|
+
* @UseGuards(OAuthAuthGuard)
|
|
22
|
+
* export class ApiController {
|
|
23
|
+
* @Get('token-info')
|
|
24
|
+
* getTokenInfo(@CurrentToken() token: string) {
|
|
25
|
+
* return {
|
|
26
|
+
* token: token.substring(0, 20) + '...',
|
|
27
|
+
* length: token.length,
|
|
28
|
+
* };
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
exports.CurrentToken = (0, common_1.createParamDecorator)((data, ctx) => {
|
|
34
|
+
const request = ctx.switchToHttp().getRequest();
|
|
35
|
+
return request.token || null;
|
|
36
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current User Decorator
|
|
3
|
+
*
|
|
4
|
+
* Parameter decorator to extract the current authenticated user from the request.
|
|
5
|
+
* Requires that a guard has set request.user (e.g., OAuthAuthGuard).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Current User Decorator
|
|
9
|
+
*
|
|
10
|
+
* Extracts the authenticated user from request.user.
|
|
11
|
+
* Returns null if no user is authenticated.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Controller('users')
|
|
16
|
+
* @UseGuards(OAuthAuthGuard)
|
|
17
|
+
* export class UsersController {
|
|
18
|
+
* @Get('profile')
|
|
19
|
+
* getProfile(@CurrentUser() user: User) {
|
|
20
|
+
* return user;
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const CurrentUser: any;
|
|
26
|
+
//# sourceMappingURL=current-user.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"current-user.decorator.d.ts","sourceRoot":"","sources":["../../../src/nestjs/decorators/current-user.decorator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,WAAW,KAKvB,CAAC"}
|