viewlogic 1.0.3 → 1.0.4

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.
@@ -1,505 +0,0 @@
1
- /**
2
- * ViewLogic Authentication Management System
3
- * 인증 관리 시스템
4
- */
5
- export class AuthManager {
6
- constructor(router, options = {}) {
7
- this.config = {
8
- enabled: options.authEnabled || false,
9
- loginRoute: options.loginRoute || 'login',
10
- protectedRoutes: options.protectedRoutes || [],
11
- protectedPrefixes: options.protectedPrefixes || [],
12
- publicRoutes: options.publicRoutes || ['login', 'register', 'home'],
13
- checkAuthFunction: options.checkAuthFunction || null,
14
- redirectAfterLogin: options.redirectAfterLogin || 'home',
15
- // 쿠키/스토리지 설정
16
- authCookieName: options.authCookieName || 'authToken',
17
- authFallbackCookieNames: options.authFallbackCookieNames || ['accessToken', 'token', 'jwt'],
18
- authStorage: options.authStorage || 'cookie',
19
- authCookieOptions: options.authCookieOptions || {},
20
- authSkipValidation: options.authSkipValidation || false,
21
- debug: options.debug || false
22
- };
23
-
24
- // 라우터 인스턴스 참조 (필수 의존성)
25
- this.router = router;
26
-
27
- // 이벤트 리스너들
28
- this.eventListeners = new Map();
29
-
30
- this.log('info', 'AuthManager initialized', { enabled: this.config.enabled });
31
- }
32
-
33
- /**
34
- * 로깅 래퍼 메서드
35
- */
36
- log(level, ...args) {
37
- if (this.router?.errorHandler) {
38
- this.router.errorHandler.log(level, 'AuthManager', ...args);
39
- }
40
- }
41
-
42
- /**
43
- * 라우트 인증 확인
44
- */
45
- async checkAuthentication(routeName) {
46
- // 인증 시스템이 비활성화된 경우
47
- if (!this.config.enabled) {
48
- return { allowed: true, reason: 'auth_disabled' };
49
- }
50
-
51
- this.log('debug', `🔐 Checking authentication for route: ${routeName}`);
52
-
53
- // 공개 라우트인지 확인
54
- if (this.isPublicRoute(routeName)) {
55
- return { allowed: true, reason: 'public_route' };
56
- }
57
-
58
- // 보호된 라우트인지 확인
59
- const isProtected = this.isProtectedRoute(routeName);
60
- if (!isProtected) {
61
- return { allowed: true, reason: 'not_protected' };
62
- }
63
-
64
- // 사용자 정의 인증 체크 함수가 있는 경우
65
- if (typeof this.config.checkAuthFunction === 'function') {
66
- try {
67
- const isAuthenticated = await this.config.checkAuthFunction(routeName);
68
- return {
69
- allowed: isAuthenticated,
70
- reason: isAuthenticated ? 'custom_auth_success' : 'custom_auth_failed',
71
- routeName
72
- };
73
- } catch (error) {
74
- this.log('error', 'Custom auth function failed:', error);
75
- return { allowed: false, reason: 'custom_auth_error', error };
76
- }
77
- }
78
-
79
- // 기본 인증 확인
80
- const isAuthenticated = this.isUserAuthenticated();
81
- return {
82
- allowed: isAuthenticated,
83
- reason: isAuthenticated ? 'authenticated' : 'not_authenticated',
84
- routeName
85
- };
86
- }
87
-
88
- /**
89
- * 사용자 인증 상태 확인
90
- */
91
- isUserAuthenticated() {
92
- this.log('debug', '🔍 Checking user authentication status');
93
-
94
- // 1. localStorage 확인
95
- const token = localStorage.getItem('authToken') || localStorage.getItem('accessToken');
96
- if (token) {
97
- try {
98
- if (token.includes('.')) {
99
- const payload = JSON.parse(atob(token.split('.')[1]));
100
- if (payload.exp && Date.now() >= payload.exp * 1000) {
101
- this.log('debug', 'localStorage token expired, removing...');
102
- localStorage.removeItem('authToken');
103
- localStorage.removeItem('accessToken');
104
- return false;
105
- }
106
- }
107
- this.log('debug', '✅ Valid token found in localStorage');
108
- return true;
109
- } catch (error) {
110
- this.log('warn', 'Invalid token in localStorage:', error);
111
- }
112
- }
113
-
114
- // 2. sessionStorage 확인
115
- const sessionToken = sessionStorage.getItem('authToken') || sessionStorage.getItem('accessToken');
116
- if (sessionToken) {
117
- this.log('debug', '✅ Token found in sessionStorage');
118
- return true;
119
- }
120
-
121
- // 3. 쿠키 확인
122
- const authCookie = this.getAuthCookie();
123
- if (authCookie) {
124
- try {
125
- if (authCookie.includes('.')) {
126
- const payload = JSON.parse(atob(authCookie.split('.')[1]));
127
- if (payload.exp && Date.now() >= payload.exp * 1000) {
128
- this.log('debug', 'Cookie token expired, removing...');
129
- this.removeAuthCookie();
130
- return false;
131
- }
132
- }
133
- this.log('debug', '✅ Valid token found in cookies');
134
- return true;
135
- } catch (error) {
136
- this.log('warn', 'Cookie token validation failed:', error);
137
- }
138
- }
139
-
140
- // 4. 전역 변수 확인 (레거시 지원)
141
- if (window.user || window.isAuthenticated) {
142
- this.log('debug', '✅ Global authentication variable found');
143
- return true;
144
- }
145
-
146
- this.log('debug', '❌ No valid authentication found');
147
- return false;
148
- }
149
-
150
- /**
151
- * 공개 라우트인지 확인
152
- */
153
- isPublicRoute(routeName) {
154
- return this.config.publicRoutes.includes(routeName);
155
- }
156
-
157
- /**
158
- * 보호된 라우트인지 확인
159
- */
160
- isProtectedRoute(routeName) {
161
- // 특정 라우트가 보호된 라우트 목록에 있는지 확인
162
- if (this.config.protectedRoutes.includes(routeName)) {
163
- return true;
164
- }
165
-
166
- // prefix로 보호된 라우트인지 확인
167
- for (const prefix of this.config.protectedPrefixes) {
168
- if (routeName.startsWith(prefix)) {
169
- return true;
170
- }
171
- }
172
-
173
- return false;
174
- }
175
-
176
- /**
177
- * 인증 쿠키 가져오기
178
- */
179
- getAuthCookie() {
180
- // 주 쿠키 이름 확인
181
- const primaryCookie = this.getCookieValue(this.config.authCookieName);
182
- if (primaryCookie) {
183
- return primaryCookie;
184
- }
185
-
186
- // 대체 쿠키 이름들 확인
187
- for (const cookieName of this.config.authFallbackCookieNames) {
188
- const cookieValue = this.getCookieValue(cookieName);
189
- if (cookieValue) {
190
- this.log('debug', `Found auth token in fallback cookie: ${cookieName}`);
191
- return cookieValue;
192
- }
193
- }
194
-
195
- return null;
196
- }
197
-
198
- /**
199
- * 쿠키 값 가져오기
200
- */
201
- getCookieValue(name) {
202
- const value = `; ${document.cookie}`;
203
- const parts = value.split(`; ${name}=`);
204
- if (parts.length === 2) {
205
- return decodeURIComponent(parts.pop().split(';').shift());
206
- }
207
- return null;
208
- }
209
-
210
- /**
211
- * 인증 쿠키 제거
212
- */
213
- removeAuthCookie() {
214
- const cookiesToRemove = [this.config.authCookieName, ...this.config.authFallbackCookieNames];
215
-
216
- cookiesToRemove.forEach(cookieName => {
217
- // 현재 경로와 루트 경로에서 모두 제거
218
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
219
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${window.location.pathname};`;
220
- });
221
-
222
- this.log('debug', 'Auth cookies removed');
223
- }
224
-
225
- /**
226
- * 액세스 토큰 가져오기
227
- */
228
- getAccessToken() {
229
- // localStorage 확인
230
- let token = localStorage.getItem('authToken') || localStorage.getItem('accessToken');
231
- if (token) return token;
232
-
233
- // sessionStorage 확인
234
- token = sessionStorage.getItem('authToken') || sessionStorage.getItem('accessToken');
235
- if (token) return token;
236
-
237
- // 쿠키 확인
238
- token = this.getAuthCookie();
239
- if (token) return token;
240
-
241
- return null;
242
- }
243
-
244
- /**
245
- * 액세스 토큰 설정
246
- */
247
- setAccessToken(token, options = {}) {
248
- if (!token) {
249
- this.log('warn', 'Empty token provided');
250
- return false;
251
- }
252
-
253
- const {
254
- storage = this.config.authStorage,
255
- cookieOptions = this.config.authCookieOptions,
256
- skipValidation = this.config.authSkipValidation
257
- } = options;
258
-
259
- try {
260
- // JWT 토큰 검증 (옵션)
261
- if (!skipValidation && token.includes('.')) {
262
- try {
263
- const payload = JSON.parse(atob(token.split('.')[1]));
264
- if (payload.exp && Date.now() >= payload.exp * 1000) {
265
- this.log('warn', '❌ Token is expired');
266
- return false;
267
- }
268
- this.log('debug', '✅ JWT token validated');
269
- } catch (error) {
270
- this.log('warn', '⚠️ JWT validation failed, but proceeding:', error.message);
271
- }
272
- }
273
-
274
- // 스토리지별 저장
275
- switch (storage) {
276
- case 'localStorage':
277
- localStorage.setItem('authToken', token);
278
- this.log('debug', 'Token saved to localStorage');
279
- break;
280
-
281
- case 'sessionStorage':
282
- sessionStorage.setItem('authToken', token);
283
- this.log('debug', 'Token saved to sessionStorage');
284
- break;
285
-
286
- case 'cookie':
287
- this.setAuthCookie(token, cookieOptions);
288
- break;
289
-
290
- default:
291
- // 기본값: localStorage
292
- localStorage.setItem('authToken', token);
293
- this.log('debug', 'Token saved to localStorage (default)');
294
- }
295
-
296
- this.emitAuthEvent('token_set', {
297
- storage,
298
- tokenLength: token.length,
299
- hasExpiration: token.includes('.')
300
- });
301
-
302
- return true;
303
-
304
- } catch (error) {
305
- this.log('Failed to set token:', error);
306
- return false;
307
- }
308
- }
309
-
310
- /**
311
- * 인증 쿠키 설정
312
- */
313
- setAuthCookie(token, options = {}) {
314
- const {
315
- cookieName = this.config.authCookieName,
316
- secure = window.location.protocol === 'https:',
317
- sameSite = 'Strict',
318
- path = '/',
319
- domain = null
320
- } = options;
321
-
322
- let cookieString = `${cookieName}=${encodeURIComponent(token)}; path=${path}`;
323
-
324
- if (secure) {
325
- cookieString += '; Secure';
326
- }
327
-
328
- if (sameSite) {
329
- cookieString += `; SameSite=${sameSite}`;
330
- }
331
-
332
- if (domain) {
333
- cookieString += `; Domain=${domain}`;
334
- }
335
-
336
- // JWT에서 만료 시간 추출
337
- try {
338
- if (token.includes('.')) {
339
- try {
340
- const payload = JSON.parse(atob(token.split('.')[1]));
341
- if (payload.exp) {
342
- const expireDate = new Date(payload.exp * 1000);
343
- cookieString += `; Expires=${expireDate.toUTCString()}`;
344
- }
345
- } catch (error) {
346
- this.log('Could not extract expiration from JWT token');
347
- }
348
- }
349
- } catch (error) {
350
- this.log('Token processing error:', error);
351
- }
352
-
353
- document.cookie = cookieString;
354
- this.log(`Auth cookie set: ${cookieName}`);
355
- }
356
-
357
- /**
358
- * 토큰 제거
359
- */
360
- removeAccessToken(storage = 'all') {
361
- switch (storage) {
362
- case 'localStorage':
363
- localStorage.removeItem('authToken');
364
- localStorage.removeItem('accessToken');
365
- break;
366
-
367
- case 'sessionStorage':
368
- sessionStorage.removeItem('authToken');
369
- sessionStorage.removeItem('accessToken');
370
- break;
371
-
372
- case 'cookie':
373
- this.removeAuthCookie();
374
- break;
375
-
376
- case 'all':
377
- default:
378
- localStorage.removeItem('authToken');
379
- localStorage.removeItem('accessToken');
380
- sessionStorage.removeItem('authToken');
381
- sessionStorage.removeItem('accessToken');
382
- this.removeAuthCookie();
383
- break;
384
- }
385
-
386
- this.emitAuthEvent('token_removed', { storage });
387
- this.log(`Token removed from: ${storage}`);
388
- }
389
-
390
- /**
391
- * 로그인 성공 처리
392
- */
393
- handleLoginSuccess(targetRoute = null) {
394
- const redirectRoute = targetRoute || this.config.redirectAfterLogin;
395
-
396
- this.log(`🎉 Login success, redirecting to: ${redirectRoute}`);
397
-
398
- this.emitAuthEvent('login_success', { targetRoute: redirectRoute });
399
-
400
- // 라우터 인스턴스가 있으면 직접 네비게이션
401
- if (this.router && typeof this.router.navigateTo === 'function') {
402
- this.router.navigateTo(redirectRoute);
403
- }
404
-
405
- return redirectRoute;
406
- }
407
-
408
- /**
409
- * 로그아웃 처리
410
- */
411
- handleLogout() {
412
- this.log('👋 Logging out user');
413
-
414
- // 모든 저장소에서 토큰 제거
415
- this.removeAccessToken();
416
-
417
- // 전역 변수 정리
418
- if (window.user) window.user = null;
419
- if (window.isAuthenticated) window.isAuthenticated = false;
420
-
421
- this.emitAuthEvent('logout', {});
422
-
423
- // 라우터 인스턴스가 있으면 직접 네비게이션
424
- if (this.router && typeof this.router.navigateTo === 'function') {
425
- this.router.navigateTo(this.config.loginRoute);
426
- }
427
-
428
- return this.config.loginRoute;
429
- }
430
-
431
- /**
432
- * 인증 이벤트 발생
433
- */
434
- emitAuthEvent(eventType, data) {
435
- const event = new CustomEvent('router:auth', {
436
- detail: {
437
- type: eventType,
438
- timestamp: Date.now(),
439
- ...data
440
- }
441
- });
442
-
443
- document.dispatchEvent(event);
444
-
445
- // 내부 리스너들에게도 알림
446
- if (this.eventListeners.has(eventType)) {
447
- this.eventListeners.get(eventType).forEach(listener => {
448
- try {
449
- listener(data);
450
- } catch (error) {
451
- this.log('Event listener error:', error);
452
- }
453
- });
454
- }
455
-
456
- this.log(`🔔 Auth event emitted: ${eventType}`, data);
457
- }
458
-
459
- /**
460
- * 이벤트 리스너 등록
461
- */
462
- on(eventType, listener) {
463
- if (!this.eventListeners.has(eventType)) {
464
- this.eventListeners.set(eventType, []);
465
- }
466
- this.eventListeners.get(eventType).push(listener);
467
- }
468
-
469
- /**
470
- * 이벤트 리스너 제거
471
- */
472
- off(eventType, listener) {
473
- if (this.eventListeners.has(eventType)) {
474
- const listeners = this.eventListeners.get(eventType);
475
- const index = listeners.indexOf(listener);
476
- if (index > -1) {
477
- listeners.splice(index, 1);
478
- }
479
- }
480
- }
481
-
482
- /**
483
- * 인증 상태 통계
484
- */
485
- getAuthStats() {
486
- return {
487
- enabled: this.config.enabled,
488
- isAuthenticated: this.isUserAuthenticated(),
489
- hasToken: !!this.getAccessToken(),
490
- protectedRoutesCount: this.config.protectedRoutes.length,
491
- protectedPrefixesCount: this.config.protectedPrefixes.length,
492
- publicRoutesCount: this.config.publicRoutes.length,
493
- storage: this.config.authStorage,
494
- loginRoute: this.config.loginRoute
495
- };
496
- }
497
-
498
- /**
499
- * 정리 (메모리 누수 방지)
500
- */
501
- destroy() {
502
- this.eventListeners.clear();
503
- this.log('debug', 'AuthManager destroyed');
504
- }
505
- }