Skip to content

Commit d81eb3e

Browse files
committed
fix: Apple OAuth 전략 개선 - profile 객체 우선 처리 및 정보 추출 로직 최적화
- passport-apple이 제공하는 profile 객체를 우선적으로 처리 - 중복된 정보 추출 로직 제거 및 조건부 처리 추가 - JWT 디코딩과 req.body.user 처리를 보조적으로 활용 - 로그 메시지 간소화 및 가독성 개선
1 parent 42a4cb1 commit d81eb3e

File tree

3 files changed

+184
-45
lines changed

3 files changed

+184
-45
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@
3232
"@nestjs/swagger": "^11.1.2",
3333
"@nestjs/typeorm": "^11.0.0",
3434
"@types/jsonwebtoken": "^9.0.9",
35-
"axios": "^1.8.4",
35+
"axios": "^1.9.0",
3636
"cache-manager": "^6.4.3",
3737
"cache-manager-redis-store": "^3.0.1",
3838
"date-fns": "^4.1.0",
3939
"jsonwebtoken": "^9.0.2",
40+
"jwks-rsa": "^3.2.0",
4041
"keyv": "^5.3.3",
4142
"lodash": "^4.17.21",
4243
"multer": "^1.4.5-lts.2",

src/auth/strategies/apple.strategy.ts

Lines changed: 97 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -66,58 +66,63 @@ export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
6666
done: any,
6767
): Promise<any> {
6868
this.logger.log('Apple 로그인 검증 시작');
69-
this.logger.log(`요청 본문: ${JSON.stringify(request.body)}`);
69+
this.logger.log(`요청 본문: ${JSON.stringify(request.body, null, 2)}`);
70+
this.logger.log(`요청 쿼리: ${JSON.stringify(request.query, null, 2)}`);
7071
this.logger.log(`accessToken: ${accessToken ? 'present' : 'not present'}`);
72+
this.logger.log(
73+
`refreshToken: ${refreshToken ? 'present' : 'not present'}`,
74+
);
7175
this.logger.log(`idToken 유형: ${typeof idToken}`);
76+
this.logger.log(`profile 유형: ${typeof profile}`);
77+
this.logger.log(`profile 내용: ${JSON.stringify(profile, null, 2)}`);
7278

7379
try {
7480
let email = '';
7581
let providerId = '';
7682
let fullName = '';
7783

78-
// 1. 첫 번째 로그인 시 req.body.user에서 사용자 정보 추출
79-
if (request.body && request.body.user) {
80-
this.logger.log('첫 번째 로그인: req.body.user에서 사용자 정보 추출');
81-
const userData = JSON.parse(request.body.user);
82-
this.logger.log(`사용자 데이터: ${JSON.stringify(userData)}`);
84+
// 1. profile에서 기본 정보 추출 (passport-apple이 이미 처리한 정보)
85+
if (profile) {
86+
this.logger.log(`profile 정보: ${JSON.stringify(profile, null, 2)}`);
87+
88+
// providerId는 profile.id에서 가져옴
89+
if (profile.id) {
90+
providerId = profile.id;
91+
this.logger.log(`profile에서 providerId 추출: ${providerId}`);
92+
}
8393

84-
if (userData.email) {
85-
email = userData.email;
86-
this.logger.log(`req.body.user에서 이메일 추출: ${email}`);
94+
// 이메일 추출
95+
if (profile.email) {
96+
email = profile.email;
97+
this.logger.log(`profile에서 이메일 추출: ${email}`);
8798
}
8899

89-
if (userData.name) {
90-
const firstName = userData.name.firstName || '';
91-
const lastName = userData.name.lastName || '';
100+
// 이름 추출
101+
if (profile.name) {
102+
const firstName = profile.name.firstName || '';
103+
const lastName = profile.name.lastName || '';
92104
fullName = `${firstName} ${lastName}`.trim();
93-
this.logger.log(`req.body.user에서 이름 추출: ${fullName}`);
105+
this.logger.log(`profile에서 이름 추출: ${fullName}`);
94106
}
95107
}
96108

97-
// 2. idToken에서 정보 추출 (JWT 디코딩)
98-
if (idToken) {
109+
// 2. idToken에서 추가 정보 추출 (JWT 디코딩)
110+
if (idToken && typeof idToken === 'string') {
99111
try {
100-
let decodedToken;
101-
102-
// idToken이 문자열인 경우 JWT 디코딩
103-
if (typeof idToken === 'string') {
104-
this.logger.log('idToken을 JWT로 디코딩 중...');
105-
decodedToken = jwt.decode(idToken) as any;
106-
} else if (typeof idToken === 'object') {
107-
this.logger.log('idToken이 이미 객체 형태로 제공됨');
108-
decodedToken = idToken;
109-
}
112+
this.logger.log('idToken을 JWT로 디코딩 중...');
113+
const decodedToken = jwt.decode(idToken, { json: true }) as any;
114+
this.logger.log(
115+
`JWT 디코딩 결과: ${JSON.stringify(decodedToken, null, 2)}`,
116+
);
110117

111118
if (decodedToken) {
112-
this.logger.log(`디코딩된 토큰: ${JSON.stringify(decodedToken)}`);
113-
114-
// providerId는 sub 필드에서 가져옴 (Apple 사용자 고유 ID)
115-
if (decodedToken.sub) {
119+
// providerId가 없으면 sub에서 가져옴
120+
if (!providerId && decodedToken.sub) {
116121
providerId = decodedToken.sub;
117122
this.logger.log(`idToken에서 providerId 추출: ${providerId}`);
118123
}
119124

120-
// 이메일이 없으면 idToken에서 시도
125+
// 이메일이 없으면 idToken에서 가져옴
121126
if (!email && decodedToken.email) {
122127
email = decodedToken.email;
123128
this.logger.log(`idToken에서 이메일 추출: ${email}`);
@@ -136,19 +141,73 @@ export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
136141
}
137142
}
138143
} catch (tokenError) {
139-
this.logger.error(`idToken 처리 오류: ${tokenError.message}`);
144+
this.logger.error(`idToken JWT 디코딩 오류: ${tokenError.message}`);
145+
}
146+
}
147+
148+
// 3. 첫 번째 로그인 시 req.body.user에서 사용자 정보 추출
149+
if (request.body && request.body.user) {
150+
this.logger.log('첫 번째 로그인: req.body.user에서 사용자 정보 추출');
151+
try {
152+
let userData;
153+
if (typeof request.body.user === 'string') {
154+
userData = JSON.parse(request.body.user);
155+
} else {
156+
userData = request.body.user;
157+
}
158+
159+
this.logger.log(
160+
`사용자 데이터: ${JSON.stringify(userData, null, 2)}`,
161+
);
162+
163+
if (userData.email && !email) {
164+
email = userData.email;
165+
this.logger.log(`req.body.user에서 이메일 추출: ${email}`);
166+
}
167+
168+
if (userData.name && !fullName) {
169+
const firstName = userData.name.firstName || '';
170+
const lastName = userData.name.lastName || '';
171+
fullName = `${firstName} ${lastName}`.trim();
172+
this.logger.log(`req.body.user에서 이름 추출: ${fullName}`);
173+
}
174+
} catch (parseError) {
175+
this.logger.error(`req.body.user 파싱 오류: ${parseError.message}`);
140176
}
141177
}
142178

143-
// 3. 여전히 이메일이 없으면 에러
179+
// 4. 여전히 이메일이 없으면 임시 이메일 생성
144180
if (!email) {
145-
this.logger.error('이메일 정보를 찾을 수 없습니다.');
146-
throw new Error(
147-
'Apple 로그인에서 이메일 정보를 가져올 수 없습니다. 이메일 공유를 허용해주세요.',
181+
this.logger.warn(
182+
'이메일 정보를 찾을 수 없음 - providerId 기반 임시 이메일 생성',
148183
);
184+
185+
if (providerId) {
186+
// providerId를 기반으로 임시 이메일 생성
187+
email = `apple_${providerId}@privaterelay.appleid.com`;
188+
this.logger.log(`providerId 기반 임시 이메일 생성: ${email}`);
189+
} else {
190+
// Apple에서 제공하는 code나 다른 정보로 임시 이메일 생성
191+
const code = request.body?.code;
192+
if (code) {
193+
// code를 기반으로 임시 이메일 생성
194+
const codeHash = crypto
195+
.createHash('sha256')
196+
.update(code)
197+
.digest('hex')
198+
.substring(0, 12);
199+
email = `apple_${codeHash}@privaterelay.appleid.com`;
200+
this.logger.log(`code 기반 임시 이메일 생성: ${email}`);
201+
} else {
202+
// 완전히 랜덤한 임시 이메일 생성
203+
const randomId = crypto.randomBytes(8).toString('hex');
204+
email = `apple_${randomId}@privaterelay.appleid.com`;
205+
this.logger.log(`랜덤 임시 이메일 생성: ${email}`);
206+
}
207+
}
149208
}
150209

151-
// 4. providerId가 없으면 이메일 기반으로 생성
210+
// 5. providerId가 없으면 이메일 기반으로 생성
152211
if (!providerId) {
153212
this.logger.warn(
154213
'providerId를 찾을 수 없어 이메일 기반 ID를 생성합니다',
@@ -165,7 +224,7 @@ export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
165224
this.logger.log(`이메일 기반 providerId 생성: ${providerId}`);
166225
}
167226

168-
// 5. 이름이 없으면 이메일에서 추출
227+
// 6. 이름이 없으면 이메일에서 추출
169228
if (!fullName) {
170229
fullName = email.split('@')[0];
171230
this.logger.log(`이메일에서 사용자명 생성: ${fullName}`);
@@ -183,7 +242,7 @@ export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
183242
accessToken: accessToken || 'apple_token',
184243
};
185244

186-
this.logger.log(`생성된 사용자 객체: ${JSON.stringify(user)}`);
245+
this.logger.log(`생성된 사용자 객체: ${JSON.stringify(user, null, 2)}`);
187246

188247
// 사용자 인증 처리
189248
const result = await this.authService.validateOAuthUser(user, 'apple');

yarn.lock

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,18 @@ __metadata:
20272027
languageName: node
20282028
linkType: hard
20292029

2030+
"@types/express-serve-static-core@npm:^4.17.33":
2031+
version: 4.19.6
2032+
resolution: "@types/express-serve-static-core@npm:4.19.6"
2033+
dependencies:
2034+
"@types/node": "npm:*"
2035+
"@types/qs": "npm:*"
2036+
"@types/range-parser": "npm:*"
2037+
"@types/send": "npm:*"
2038+
checksum: 10c0/4281f4ead71723f376b3ddf64868ae26244d434d9906c101cf8d436d4b5c779d01bd046e4ea0ed1a394d3e402216fabfa22b1fa4dba501061cd7c81c54045983
2039+
languageName: node
2040+
linkType: hard
2041+
20302042
"@types/express-serve-static-core@npm:^5.0.0":
20312043
version: 5.0.6
20322044
resolution: "@types/express-serve-static-core@npm:5.0.6"
@@ -2050,6 +2062,18 @@ __metadata:
20502062
languageName: node
20512063
linkType: hard
20522064

2065+
"@types/express@npm:^4.17.20":
2066+
version: 4.17.22
2067+
resolution: "@types/express@npm:4.17.22"
2068+
dependencies:
2069+
"@types/body-parser": "npm:*"
2070+
"@types/express-serve-static-core": "npm:^4.17.33"
2071+
"@types/qs": "npm:*"
2072+
"@types/serve-static": "npm:*"
2073+
checksum: 10c0/15c10a5ebb40a0356baa95ed374a2150d862786c9fccbdd724df12acc9c8cb08fbe1d34b446b1bcef2dbe5305cb3013fb39fba791baa54ef6df8056482776abb
2074+
languageName: node
2075+
linkType: hard
2076+
20532077
"@types/http-errors@npm:*":
20542078
version: 2.0.4
20552079
resolution: "@types/http-errors@npm:2.0.4"
@@ -2064,7 +2088,7 @@ __metadata:
20642088
languageName: node
20652089
linkType: hard
20662090

2067-
"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:^9.0.9":
2091+
"@types/jsonwebtoken@npm:*, @types/jsonwebtoken@npm:^9.0.4, @types/jsonwebtoken@npm:^9.0.9":
20682092
version: 9.0.9
20692093
resolution: "@types/jsonwebtoken@npm:9.0.9"
20702094
dependencies:
@@ -2971,14 +2995,14 @@ __metadata:
29712995
languageName: node
29722996
linkType: hard
29732997

2974-
"axios@npm:^1.8.4":
2975-
version: 1.8.4
2976-
resolution: "axios@npm:1.8.4"
2998+
"axios@npm:^1.9.0":
2999+
version: 1.9.0
3000+
resolution: "axios@npm:1.9.0"
29773001
dependencies:
29783002
follow-redirects: "npm:^1.15.6"
29793003
form-data: "npm:^4.0.0"
29803004
proxy-from-env: "npm:^1.1.0"
2981-
checksum: 10c0/450993c2ba975ffccaf0d480b68839a3b2435a5469a71fa2fb0b8a55cdb2c2ae47e609360b9c1e2b2534b73dfd69e2733a1cf9f8215bee0bcd729b72f801b0ce
3005+
checksum: 10c0/9371a56886c2e43e4ff5647b5c2c3c046ed0a3d13482ef1d0135b994a628c41fbad459796f101c655e62f0c161d03883454474d2e435b2e021b1924d9f24994c
29823006
languageName: node
29833007
linkType: hard
29843008

@@ -3026,7 +3050,7 @@ __metadata:
30263050
"@types/passport-naver": "npm:^1"
30273051
"@typescript-eslint/eslint-plugin": "npm:^8.29.0"
30283052
"@typescript-eslint/parser": "npm:^8.29.0"
3029-
axios: "npm:^1.8.4"
3053+
axios: "npm:^1.9.0"
30303054
bcrypt: "npm:^5.1.1"
30313055
cache-manager: "npm:^6.4.3"
30323056
cache-manager-redis-store: "npm:^3.0.1"
@@ -3037,6 +3061,7 @@ __metadata:
30373061
eslint-config-prettier: "npm:^10.1.1"
30383062
eslint-plugin-prettier: "npm:^5.2.6"
30393063
jsonwebtoken: "npm:^9.0.2"
3064+
jwks-rsa: "npm:^3.2.0"
30403065
keyv: "npm:^5.3.3"
30413066
lodash: "npm:^4.17.21"
30423067
multer: "npm:^1.4.5-lts.2"
@@ -6272,6 +6297,13 @@ __metadata:
62726297
languageName: node
62736298
linkType: hard
62746299

6300+
"jose@npm:^4.15.4":
6301+
version: 4.15.9
6302+
resolution: "jose@npm:4.15.9"
6303+
checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4
6304+
languageName: node
6305+
linkType: hard
6306+
62756307
"js-beautify@npm:^1.6.14":
62766308
version: 1.15.4
62776309
resolution: "js-beautify@npm:1.15.4"
@@ -6488,6 +6520,20 @@ __metadata:
64886520
languageName: node
64896521
linkType: hard
64906522

6523+
"jwks-rsa@npm:^3.2.0":
6524+
version: 3.2.0
6525+
resolution: "jwks-rsa@npm:3.2.0"
6526+
dependencies:
6527+
"@types/express": "npm:^4.17.20"
6528+
"@types/jsonwebtoken": "npm:^9.0.4"
6529+
debug: "npm:^4.3.4"
6530+
jose: "npm:^4.15.4"
6531+
limiter: "npm:^1.1.5"
6532+
lru-memoizer: "npm:^2.2.0"
6533+
checksum: 10c0/94896264473c8ec0ec21b8f29fd69b760ccb58ff63e6d5328d99694dc49a9be1d6f739fa536c71ca279966874e6c77b405181ed2c567318e0f545d3e941c318e
6534+
languageName: node
6535+
linkType: hard
6536+
64916537
"jws@npm:^3.2.2":
64926538
version: 3.2.2
64936539
resolution: "jws@npm:3.2.2"
@@ -6699,6 +6745,13 @@ __metadata:
66996745
languageName: node
67006746
linkType: hard
67016747

6748+
"limiter@npm:^1.1.5":
6749+
version: 1.1.5
6750+
resolution: "limiter@npm:1.1.5"
6751+
checksum: 10c0/ebe2b20a820d1f67b8e1724051246434c419b2da041a7e9cd943f6daf113b8d17a52a1bd88fb79be5b624c10283ecb737f50edb5c1c88c71f4cd367108c97300
6752+
languageName: node
6753+
linkType: hard
6754+
67026755
"lines-and-columns@npm:^1.1.6":
67036756
version: 1.2.4
67046757
resolution: "lines-and-columns@npm:1.2.4"
@@ -6779,6 +6832,13 @@ __metadata:
67796832
languageName: node
67806833
linkType: hard
67816834

6835+
"lodash.clonedeep@npm:^4.5.0":
6836+
version: 4.5.0
6837+
resolution: "lodash.clonedeep@npm:4.5.0"
6838+
checksum: 10c0/2caf0e4808f319d761d2939ee0642fa6867a4bbf2cfce43276698828380756b99d4c4fa226d881655e6ac298dd453fe12a5ec8ba49861777759494c534936985
6839+
languageName: node
6840+
linkType: hard
6841+
67826842
"lodash.escaperegexp@npm:^4.1.2":
67836843
version: 4.1.2
67846844
resolution: "lodash.escaperegexp@npm:4.1.2"
@@ -6880,6 +6940,15 @@ __metadata:
68806940
languageName: node
68816941
linkType: hard
68826942

6943+
"lru-cache@npm:6.0.0":
6944+
version: 6.0.0
6945+
resolution: "lru-cache@npm:6.0.0"
6946+
dependencies:
6947+
yallist: "npm:^4.0.0"
6948+
checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9
6949+
languageName: node
6950+
linkType: hard
6951+
68836952
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.2.2":
68846953
version: 10.4.3
68856954
resolution: "lru-cache@npm:10.4.3"
@@ -6901,6 +6970,16 @@ __metadata:
69016970
languageName: node
69026971
linkType: hard
69036972

6973+
"lru-memoizer@npm:^2.2.0":
6974+
version: 2.3.0
6975+
resolution: "lru-memoizer@npm:2.3.0"
6976+
dependencies:
6977+
lodash.clonedeep: "npm:^4.5.0"
6978+
lru-cache: "npm:6.0.0"
6979+
checksum: 10c0/13cf6bc9ff74cdb167078dbb66d4cf43adc802495da8f56097e6f388b4d7ccb91668beb809bdbc55b62d016c138d7c19a18c5883a2fdbcc7f508ad8a23ec7c65
6980+
languageName: node
6981+
linkType: hard
6982+
69046983
"lru.min@npm:^1.0.0":
69056984
version: 1.1.2
69066985
resolution: "lru.min@npm:1.1.2"

0 commit comments

Comments
 (0)