박권수

feat. social Login & social Register

...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
12 "@google-cloud/storage": "^5.14.2", 12 "@google-cloud/storage": "^5.14.2",
13 "@koa/cors": "^3.1.0", 13 "@koa/cors": "^3.1.0",
14 "firebase-admin": "^9.11.1", 14 "firebase-admin": "^9.11.1",
15 + "google-auth-library": "^7.10.0",
15 "koa-body": "^4.2.0", 16 "koa-body": "^4.2.0",
16 "moment": "^2.29.1", 17 "moment": "^2.29.1",
17 "moment-timezone": "^0.5.33", 18 "moment-timezone": "^0.5.33",
...@@ -1741,9 +1742,9 @@ ...@@ -1741,9 +1742,9 @@
1741 } 1742 }
1742 }, 1743 },
1743 "node_modules/google-auth-library": { 1744 "node_modules/google-auth-library": {
1744 - "version": "7.9.2", 1745 + "version": "7.10.0",
1745 - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", 1746 + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz",
1746 - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", 1747 + "integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==",
1747 "dependencies": { 1748 "dependencies": {
1748 "arrify": "^2.0.0", 1749 "arrify": "^2.0.0",
1749 "base64-js": "^1.3.0", 1750 "base64-js": "^1.3.0",
...@@ -5182,9 +5183,9 @@ ...@@ -5182,9 +5183,9 @@
5182 } 5183 }
5183 }, 5184 },
5184 "google-auth-library": { 5185 "google-auth-library": {
5185 - "version": "7.9.2", 5186 + "version": "7.10.0",
5186 - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", 5187 + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz",
5187 - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", 5188 + "integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==",
5188 "requires": { 5189 "requires": {
5189 "arrify": "^2.0.0", 5190 "arrify": "^2.0.0",
5190 "base64-js": "^1.3.0", 5191 "base64-js": "^1.3.0",
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
20 "@google-cloud/storage": "^5.14.2", 20 "@google-cloud/storage": "^5.14.2",
21 "@koa/cors": "^3.1.0", 21 "@koa/cors": "^3.1.0",
22 "firebase-admin": "^9.11.1", 22 "firebase-admin": "^9.11.1",
23 + "google-auth-library": "^7.10.0",
23 "koa-body": "^4.2.0", 24 "koa-body": "^4.2.0",
24 "moment": "^2.29.1", 25 "moment": "^2.29.1",
25 "moment-timezone": "^0.5.33", 26 "moment-timezone": "^0.5.33",
......
...@@ -93,6 +93,7 @@ exports.searchHospital = async ctx => { ...@@ -93,6 +93,7 @@ exports.searchHospital = async ctx => {
93 }; 93 };
94 }; 94 };
95 95
96 +//의사 회원가입
96 exports.doctorRegister = async ctx => { 97 exports.doctorRegister = async ctx => {
97 const { 98 const {
98 userId, 99 userId,
...@@ -193,6 +194,7 @@ exports.doctorRegister = async ctx => { ...@@ -193,6 +194,7 @@ exports.doctorRegister = async ctx => {
193 194
194 } 195 }
195 196
197 +//로컬 로그인
196 exports.login = async(ctx) => { 198 exports.login = async(ctx) => {
197 const { userId, password, deviceToken } = ctx.request.body; 199 const { userId, password, deviceToken } = ctx.request.body;
198 200
...@@ -211,7 +213,7 @@ exports.login = async(ctx) => { ...@@ -211,7 +213,7 @@ exports.login = async(ctx) => {
211 } 213 }
212 214
213 const user = await User.findByUserId(userId); 215 const user = await User.findByUserId(userId);
214 - if(!user || !user.userTypeCd) { 216 + if(!user || !user.userTypeCd || user.authTypeCd !== 'NORMAL') {
215 ctx.status = 401; 217 ctx.status = 401;
216 ctx.body = { 218 ctx.body = {
217 error : '존재하지 않는 회원입니다.', 219 error : '존재하지 않는 회원입니다.',
...@@ -259,6 +261,180 @@ exports.login = async(ctx) => { ...@@ -259,6 +261,180 @@ exports.login = async(ctx) => {
259 261
260 }; 262 };
261 263
264 +//social Register
265 +exports.socialRegister = async ctx => {
266 + const { socialType } = ctx.params;
267 + const { accessToken, deviceToken } = ctx.request.body;
268 +
269 + const verifyingToken =
270 + socialType.toUpperCase() === 'GOOGLE' ? async () => {
271 + //id_token
272 + const result = jwt.decode(accessToken);
273 +
274 + return {
275 + userId : result.email,
276 + userNm : result.name,
277 + contact : null,
278 + birth : null,
279 + };
280 + }
281 + : socialType.toUpperCase() === 'NAVER' ? async () => {
282 + const url = 'https://openapi.naver.com/v1/nid/me';
283 + const result = await axios.get(url, {
284 + headers : {
285 + Authorization : `Bearer ${accessToken}`,
286 + },
287 + });
288 +
289 + const { email, mobile, name, birthday, birthyear } = result.data.response;
290 +
291 + return {
292 + userId : email,
293 + userNm : name,
294 + contact : mobile,
295 + birth : `${birthyear}-${birthday}`,
296 + };
297 + }
298 + : socialType.toUpperCase() === 'KAKAO' ? async () => {
299 + const url = 'https://kapi.kakao.com/v2/user/me';
300 + const result = await axios.get(url, {
301 + headers : {
302 + Authorization : `Bearer ${accessToken}`,
303 + },
304 + });
305 +
306 + console.log(result);
307 +
308 + return result;
309 + } : () => null;
310 +
311 +
312 + const verifyingInfo = await verifyingToken();
313 + if(!verifyingInfo || !verifyingInfo.userId) {
314 + ctx.status = 403;
315 + ctx.body = {
316 + error : '잘못된 요청',
317 + };
318 +
319 + return;
320 + }
321 +
322 + const { userId, userNm, birth, contact } = verifyingInfo;
323 +
324 + const existUser = await User.findByUserId(userId);
325 + if(existUser) {
326 + ctx.status = 409;
327 + ctx.body = {
328 + error : '이미 가입된 회원',
329 + };
330 +
331 + return;
332 + }
333 +
334 + const user = new User({
335 + userId,
336 + hashedPassword : null,
337 + authTypeCd : socialType.toUpperCase(),
338 + useYn : 'Y',
339 + });
340 +
341 + const profile = new Profile({
342 + userId,
343 + userNm,
344 + birth,
345 + contact,
346 + deviceToken,
347 + });
348 +
349 + await user.save();
350 + await profile.save();
351 +
352 + ctx.status = 201;
353 +
354 +};
355 +
356 +//social Login
357 +exports.socialLogin = async ctx => {
358 + const { socialType } = ctx.params;
359 + const { accessToken, deviceToken, } = ctx.request.body;
360 +
361 + const verifyingToken =
362 + socialType.toUpperCase() === 'GOOGLE' ? async () => {
363 + //id_token : google Login
364 + const result = jwt.decode(accessToken);
365 +
366 + return result.email;
367 + }
368 + : socialType.toUpperCase() === 'NAVER' ? async () => {
369 + //naver Login
370 + const url = 'https://openapi.naver.com/v1/nid/me';
371 + const result = await axios.get(url, {
372 + headers : {
373 + Authorization : `Bearer ${accessToken}`,
374 + },
375 + });
376 +
377 + return result.data.response.email;
378 + }
379 + : socialType.toUpperCase() === 'KAKAO' ? async () => {
380 + //kakao Login
381 + const url = 'https://kapi.kakao.com/v2/user/me';
382 + const result = await axios.get(url, {
383 + headers : {
384 + Authorization : `Bearer ${accessToken}`,
385 + },
386 + });
387 +
388 + console.log(result);
389 +
390 + return result;
391 + } : () => null;
392 +
393 +
394 + const userId = await verifyingToken();
395 + if(!userId) {
396 + ctx.status = 403;
397 + ctx.body = {
398 + error : '잘못된 요청입니다',
399 + };
400 +
401 + return;
402 + }
403 +
404 + const user = await User.findByUserId(userId);
405 + if(!user || user.useYn !== 'Y') {
406 + ctx.status = 404;
407 + ctx.body = {
408 + error : '존재하지 않는 회원입니다.',
409 + };
410 +
411 + return;
412 + } else if (user.authTypeCd !== socialType.toUpperCase()) {
413 + ctx.status = 400;
414 + ctx.body = {
415 + error : '잘못된 소셜 로그인입니다.',
416 + };
417 +
418 + return;
419 + }
420 +
421 + const profile = await Profile.findOne({ userId });
422 + if(profile.deviceToken !== deviceToken) {
423 + profile.updateDeviceToken(deviceToken);
424 + await profile.save();
425 + }
426 +
427 +
428 + const token = await user.generateToken();
429 +
430 + ctx.status = 200;
431 + ctx.body = {
432 + userTypeCd : user.userTypeCd,
433 + token,
434 + };
435 +
436 +};
437 +
262 exports.logout = async(ctx) => { 438 exports.logout = async(ctx) => {
263 ctx.cookies.set('access_token', null, { 439 ctx.cookies.set('access_token', null, {
264 httpOnly : true, 440 httpOnly : true,
......
...@@ -5,7 +5,7 @@ const authCtrl = require('./auth.ctrl') ...@@ -5,7 +5,7 @@ const authCtrl = require('./auth.ctrl')
5 const auth = new Router() 5 const auth = new Router()
6 6
7 /** 7 /**
8 - * 회원가입 (email type) : 환자 회원가입 8 + * 로컬 회원가입 (email type) : 환자 회원가입
9 * url : http://localhost:4000/api/auth/register 9 * url : http://localhost:4000/api/auth/register
10 * request parameter : userId, password, passwordCheck 10 * request parameter : userId, password, passwordCheck
11 * return : null 11 * return : null
...@@ -21,7 +21,7 @@ auth.post('/register', authCtrl.register) ...@@ -21,7 +21,7 @@ auth.post('/register', authCtrl.register)
21 auth.get('/hospital', authCtrl.searchHospital); 21 auth.get('/hospital', authCtrl.searchHospital);
22 22
23 /** 23 /**
24 - * 회원가입 (email type) : 의사 회원가입 24 + * 로컬 회원가입 (email type) : 의사 회원가입
25 * url : http://localhost:4000/api/auth/register/doctor 25 * url : http://localhost:4000/api/auth/register/doctor
26 * request parameter : userId, password, passwordCheck, doctorInfo(File) 26 * request parameter : userId, password, passwordCheck, doctorInfo(File)
27 * return : null 27 * return : null
...@@ -29,14 +29,30 @@ auth.get('/hospital', authCtrl.searchHospital); ...@@ -29,14 +29,30 @@ auth.get('/hospital', authCtrl.searchHospital);
29 auth.post('/register/doctor', KoaBody, authCtrl.doctorRegister) 29 auth.post('/register/doctor', KoaBody, authCtrl.doctorRegister)
30 30
31 /** 31 /**
32 - * 로그인 (email type) 32 + * 로컬 로그인 (email type)
33 * url : http://localhost:4000/api/auth/login 33 * url : http://localhost:4000/api/auth/login
34 * request parameter : userId, password 34 * request parameter : userId, password
35 - * return : userId 35 + * return : token, userTypeCd
36 */ 36 */
37 auth.post('/login', authCtrl.login) 37 auth.post('/login', authCtrl.login)
38 38
39 /** 39 /**
40 + * 소셜 회원가입(Google, Naver, Kakao)
41 + * url : http://localhost:4000/api/auth/register/${socialType}
42 + * request parameter : accessToken
43 + * return : status
44 + */
45 +auth.post('/register/social/:socialType', authCtrl.socialRegister);
46 +
47 +/**
48 + * 소셜 로그인(Google, Naver, Kakao)
49 + * url : http://localhost:4000/api/auth/login/${socialType}
50 + * request parameter
51 + * return : token, userTypeCd
52 + */
53 +auth.post('/login/social/:socialType', authCtrl.socialLogin);
54 +
55 +/**
40 * 로그아웃 56 * 로그아웃
41 * url : http://localhost:4000/api/auth/logout 57 * url : http://localhost:4000/api/auth/logout
42 * request parameter : null 58 * request parameter : null
......
...@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken'); ...@@ -5,9 +5,10 @@ const jwt = require('jsonwebtoken');
5 const Schema = mongoose.Schema; 5 const Schema = mongoose.Schema;
6 6
7 const UserSchema = new Schema ({ 7 const UserSchema = new Schema ({
8 - userId : { type: String, required : true, unique : true, lowercase : true, }, 8 + userId : { type: String, required : true, unique : true, lowercase : true, trim : true },
9 - hashedPassword : { type : String, required : true }, 9 + hashedPassword : { type : String, required : true, },
10 - userTypeCd : { type : String, required : true, default : 'NORMAL' }, 10 + userTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
11 + authTypeCd : { type : String, required : true, default : 'NORMAL', uppercase : true, },
11 useYn : { type : String, default : 'W', required : true, }, 12 useYn : { type : String, default : 'W', required : true, },
12 }); 13 });
13 14
......
...@@ -1134,10 +1134,10 @@ ...@@ -1134,10 +1134,10 @@
1134 dependencies: 1134 dependencies:
1135 "type-fest" "^0.20.2" 1135 "type-fest" "^0.20.2"
1136 1136
1137 -"google-auth-library@^7.0.0", "google-auth-library@^7.0.2", "google-auth-library@^7.6.1": 1137 +"google-auth-library@^7.0.0", "google-auth-library@^7.0.2", "google-auth-library@^7.10.0", "google-auth-library@^7.6.1":
1138 - "integrity" "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==" 1138 + "integrity" "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ=="
1139 - "resolved" "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz" 1139 + "resolved" "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz"
1140 - "version" "7.9.2" 1140 + "version" "7.10.0"
1141 dependencies: 1141 dependencies:
1142 "arrify" "^2.0.0" 1142 "arrify" "^2.0.0"
1143 "base64-js" "^1.3.0" 1143 "base64-js" "^1.3.0"
......