@ahhaohho/auth-middleware 2.2.0 → 2.3.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/package.json
CHANGED
package/src/middleware/auth.js
CHANGED
|
@@ -179,8 +179,6 @@ async function authenticateHybrid(req, res, next) {
|
|
|
179
179
|
|
|
180
180
|
// 3. Refresh token 검증
|
|
181
181
|
passport.authenticate('refresh', { session: false }, async (refreshErr, refreshUser, refreshInfo) => {
|
|
182
|
-
console.log('[@ahhaohho/auth-middleware] 🔍 Refresh callback:', { hasError: !!refreshErr, hasUser: !!refreshUser, userId: refreshUser?.userId });
|
|
183
|
-
|
|
184
182
|
if (refreshErr) {
|
|
185
183
|
console.error('[@ahhaohho/auth-middleware] Refresh token error:', refreshErr.message);
|
|
186
184
|
return res.status(500).json({
|
|
@@ -4,36 +4,37 @@ const { isBlacklisted } = require('../utils/blacklist');
|
|
|
4
4
|
const jwt = require('jsonwebtoken');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* 우선순위: 1.
|
|
7
|
+
* Bearer 헤더 또는 쿠키에서 토큰을 추출하는 커스텀 함수
|
|
8
|
+
* 우선순위: 1. Authorization 헤더 (Bearer) 2. 쿠키 (flc_auth_token)
|
|
9
|
+
*
|
|
10
|
+
* Bearer 헤더 우선: 클라이언트가 명시적으로 설정한 토큰이 가장 신선함.
|
|
11
|
+
* 쿠키는 브라우저가 자동 전송하므로 stale 토큰이 남아있을 수 있음.
|
|
9
12
|
*/
|
|
10
13
|
const extractJwtFromRequest = (req) => {
|
|
11
|
-
// 1.
|
|
12
|
-
if (req.cookies && req.cookies.flc_auth_token) {
|
|
13
|
-
return req.cookies.flc_auth_token;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// 2. Authorization 헤더에서 토큰 확인 (기존 방식 호환)
|
|
14
|
+
// 1. Authorization 헤더에서 토큰 확인 (Bearer 방식 - 우선)
|
|
17
15
|
const authHeader = req.headers.authorization || req.headers.Authorization;
|
|
18
16
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
if (authHeader) {
|
|
18
|
+
// Bearer 접두사가 있는 경우
|
|
19
|
+
if (authHeader.startsWith('Bearer ')) {
|
|
20
|
+
return authHeader.substring(7);
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
// bearer (소문자)인 경우
|
|
24
|
+
if (authHeader.startsWith('bearer ')) {
|
|
25
|
+
return authHeader.substring(7);
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Bearer 접두사 없이 토큰만 있는 경우
|
|
29
|
+
// JWT 형식인지 확인 (xxx.yyy.zzz 형태)
|
|
30
|
+
if (authHeader.split('.').length === 3) {
|
|
31
|
+
return authHeader;
|
|
32
|
+
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return authHeader;
|
|
35
|
+
// 2. 쿠키에서 FLC 토큰 확인 (HttpOnly 쿠키 방식 - 폴백)
|
|
36
|
+
if (req.cookies && req.cookies.flc_auth_token) {
|
|
37
|
+
return req.cookies.flc_auth_token;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
return null;
|
|
@@ -77,23 +78,9 @@ function createJwtStrategy() {
|
|
|
77
78
|
'[@ahhaohho/auth-middleware] ⚠️ Token verified with previous key (fallback)'
|
|
78
79
|
);
|
|
79
80
|
} catch (previousKeyError) {
|
|
80
|
-
// 🚨 임시: invalid signature도 허용 (다음 앱 배포 전까지)
|
|
81
|
-
if (currentKeyError.message.includes('invalid signature') || currentKeyError.message.includes('jwt malformed')) {
|
|
82
|
-
console.warn('[@ahhaohho/auth-middleware] ⚠️ [TEMPORARY] Allowing invalid signature');
|
|
83
|
-
request._jwtDecoded = { userId: 'unknown', userRole: 'guest' };
|
|
84
|
-
request._jwtKeyUsed = 'bypassed';
|
|
85
|
-
return done(null, keys.current);
|
|
86
|
-
}
|
|
87
81
|
return done(currentKeyError, false);
|
|
88
82
|
}
|
|
89
83
|
} else {
|
|
90
|
-
// 🚨 임시: invalid signature도 허용 (다음 앱 배포 전까지)
|
|
91
|
-
if (currentKeyError.message.includes('invalid signature') || currentKeyError.message.includes('jwt malformed')) {
|
|
92
|
-
console.warn('[@ahhaohho/auth-middleware] ⚠️ [TEMPORARY] Allowing invalid signature');
|
|
93
|
-
request._jwtDecoded = { userId: 'unknown', userRole: 'guest' };
|
|
94
|
-
request._jwtKeyUsed = 'bypassed';
|
|
95
|
-
return done(null, keys.current);
|
|
96
|
-
}
|
|
97
84
|
return done(currentKeyError, false);
|
|
98
85
|
}
|
|
99
86
|
}
|
|
@@ -3,19 +3,12 @@ const { verifyTokenWithFallback } = require('../utils/jwtValidator');
|
|
|
3
3
|
const { isBlacklisted } = require('../utils/blacklist');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* 우선순위: 1.
|
|
6
|
+
* 헤더 또는 쿠키에서 Refresh Token 추출
|
|
7
|
+
* 우선순위: 1. refresh-token 헤더 2. 쿠키 (flc_refresh_token)
|
|
8
8
|
* Bearer 접두사가 있든 없든 처리
|
|
9
9
|
*/
|
|
10
10
|
function extractRefreshToken(req) {
|
|
11
|
-
// 1.
|
|
12
|
-
if (req && req.cookies && req.cookies.flc_refresh_token) {
|
|
13
|
-
return req.cookies.flc_refresh_token;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// 2. 헤더에서 리프레시 토큰 확인 (기존 방식 호환)
|
|
17
|
-
let token = null;
|
|
18
|
-
|
|
11
|
+
// 1. 헤더에서 리프레시 토큰 확인 (우선)
|
|
19
12
|
if (req && req.headers) {
|
|
20
13
|
let refreshToken = req.headers['refresh-token'] || req.headers['refreshtoken'];
|
|
21
14
|
|
|
@@ -27,11 +20,16 @@ function extractRefreshToken(req) {
|
|
|
27
20
|
refreshToken = refreshToken.substring(7);
|
|
28
21
|
}
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
return refreshToken.trim();
|
|
31
24
|
}
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
// 2. 쿠키에서 FLC 리프레시 토큰 확인 (폴백)
|
|
28
|
+
if (req && req.cookies && req.cookies.flc_refresh_token) {
|
|
29
|
+
return req.cookies.flc_refresh_token;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
/**
|
|
@@ -46,7 +46,7 @@ class SecretManager {
|
|
|
46
46
|
return JSON.parse(cachedKeys);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// 2. AWS Secrets Manager에서 가져오기
|
|
49
|
+
// 2. AWS Secrets Manager에서 가져오기 (AWSCURRENT + AWSPREVIOUS)
|
|
50
50
|
console.log('[@ahhaohho/auth-middleware] Fetching JWT keys from AWS Secrets Manager');
|
|
51
51
|
const command = new GetSecretValueCommand({ SecretId: this.secretName });
|
|
52
52
|
const response = await this.client.send(command);
|
|
@@ -56,9 +56,36 @@ class SecretManager {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const secret = JSON.parse(response.SecretString);
|
|
59
|
+
const currentKey = secret.current || secret.jwt_secret_key || secret.dev;
|
|
60
|
+
|
|
61
|
+
// previous 키 결정: secret 내 previous 필드 → AWSPREVIOUS 버전 순서
|
|
62
|
+
let previousKey = secret.previous || null;
|
|
63
|
+
|
|
64
|
+
if (!previousKey) {
|
|
65
|
+
try {
|
|
66
|
+
const prevCommand = new GetSecretValueCommand({
|
|
67
|
+
SecretId: this.secretName,
|
|
68
|
+
VersionStage: 'AWSPREVIOUS'
|
|
69
|
+
});
|
|
70
|
+
const prevResponse = await this.client.send(prevCommand);
|
|
71
|
+
if (prevResponse.SecretString) {
|
|
72
|
+
const prevSecret = JSON.parse(prevResponse.SecretString);
|
|
73
|
+
const prevCandidate = prevSecret.current || prevSecret.jwt_secret_key || prevSecret.dev;
|
|
74
|
+
// 이전 키가 현재 키와 다를 때만 사용
|
|
75
|
+
if (prevCandidate && prevCandidate !== currentKey) {
|
|
76
|
+
previousKey = prevCandidate;
|
|
77
|
+
console.log('[@ahhaohho/auth-middleware] Using AWSPREVIOUS version as fallback key');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (prevError) {
|
|
81
|
+
// AWSPREVIOUS가 없을 수 있음 (첫 시크릿이거나 로테이션 미사용)
|
|
82
|
+
console.log('[@ahhaohho/auth-middleware] No AWSPREVIOUS version available');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
59
86
|
const keys = {
|
|
60
|
-
current:
|
|
61
|
-
previous:
|
|
87
|
+
current: currentKey,
|
|
88
|
+
previous: previousKey
|
|
62
89
|
};
|
|
63
90
|
|
|
64
91
|
// 3. Redis에 캐싱 (5분 TTL)
|