-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: oauth2 kakao login #39
Changes from 2 commits
085ad56
add1210
4e7c61c
ef25e6b
1507fc7
be463e8
a52a173
7b388f2
e13e3b7
95f4224
d757fa9
42fd86a
7e3f7c6
7746c91
11d06e1
1912109
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
"private": true, | ||
"dependencies": { | ||
"@typegoose/typegoose": "^12.9.0", | ||
"@types/jsonwebtoken": "^9.0.7", | ||
"axios": "^1.7.9", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제거 해주세요 |
||
"compression": "^1.7.5", | ||
"cors": "^2.8.5", | ||
"cross-env": "^7.0.3", | ||
|
@@ -22,6 +24,7 @@ | |
"express-winston": "^4.2.0", | ||
"helmet": "^8.0.0", | ||
"hpp": "^0.2.3", | ||
"jsonwebtoken": "^9.0.2", | ||
"mongoose": "^8.8.1", | ||
"mongoose-paginate-v2": "^1.8.5", | ||
"winston": "^3.16.0", | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,6 @@ import { config } from 'dotenv' | |||||
|
||||||
config() | ||||||
|
||||||
export const { NODE_ENV, DB_URI, DB_NAME } = process.env | ||||||
export const { NODE_ENV, DB_URI, DB_NAME, JWT_PRIVATE_KEY } = process.env | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private key 는 비대칭키 알고리즘을 의미합니다.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 수정 안됐어요 |
||||||
|
||||||
export const PORT = Number.parseInt(process.env.PORT) || 5000 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
import enquiries from './enquiries' | ||
import events from './events' | ||
import schedules from './schedules' | ||
import user from './user' | ||
|
||
export default { | ||
enquiries, | ||
events, | ||
schedules, | ||
user, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import asyncify from 'express-asyncify' | ||
import express, { Request, Response } from 'express' | ||
import { IUserInfo, JWTProvider, Kakao, OAuthPayload } from '@/utils/auth' | ||
import { User, UserModel } from '@/models/user' | ||
|
||
const router = asyncify(express.Router()) | ||
|
||
router.post('/login', async (req: Request, res: Response) => { | ||
const oauthPayload: OAuthPayload = req.body | ||
|
||
// todo: refactor this code blocks after introducing new oauth provider | ||
// ==> split the code as service, and adding handler for controller logic | ||
if (oauthPayload.provider === 'KAKAO') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각 provider 별 필요한 정보를 얻어오는 middleware 와 해당 middleware 에서 얻은 정보로 jwt 를 만드는 handler 로 분리하면 될것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 express middleware를 활용해서 추후에 작업 진행해볼게요! |
||
const userInfo: IUserInfo = await Kakao.getUserInfo(oauthPayload) | ||
const user: User = await UserModel.loadOrCreateUser(userInfo) | ||
const jwt = JWTProvider.issueJwt(user) | ||
res.status(200).json({ | ||
accessToken: jwt, | ||
}) | ||
} | ||
}) | ||
|
||
export default router |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import mongoose from 'mongoose' | ||
import { TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' | ||
import { getModelForClass, prop } from '@typegoose/typegoose' | ||
import { getModelForClass, prop, ReturnModelType } from '@typegoose/typegoose' | ||
import { IUserInfo } from '@/utils/auth' | ||
|
||
export class User extends TimeStamps { | ||
laggu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public _id: mongoose.Types.ObjectId | ||
|
@@ -13,6 +14,22 @@ export class User extends TimeStamps { | |
|
||
@prop() | ||
public providerId: string | ||
|
||
public toJSON() { | ||
return { | ||
nickname: this.nickname, | ||
provider: this.provider, | ||
providerId: this.providerId, | ||
} | ||
joonamin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public static async loadOrCreateUser(this: ReturnModelType<typeof User>, info: IUserInfo) { | ||
let user = await this.findOne({ providerId: info.providerId }).exec() | ||
if (!user) { | ||
user = await this.create(info) | ||
} | ||
return user | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 회원가입과 로그인을 분리하는게 좋을것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인 단에서의 메소드 분리만 말씀하시는것이 맞으실까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. api 분리입니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 만약 분리를 하게 된다면, 클라이언트의 입장에서
결국 사용자에게 추가적으로 요구하는 정보 ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 회원가입을 하는 과정에서 유저에게 서비스 동의를 받아야 합니다. 최초 유저가 로그인 시도시 회원 가입이 안되있으면 에러를 주고 회원 가입 화면으로 보내는게 좋을것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 ㅎㅎ 이해했습니다 리뷰에 반영할게요! |
||
} | ||
|
||
export const UserModel = getModelForClass(User) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { APIError } from '@/types/errors/error' | ||
|
||
export class HttpClientException extends APIError { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 너무 제너럴한 이름인거 같아요. |
||
constructor(cause: Error | string = null) { | ||
super(422, 4220, 'auth http client exception', cause) | ||
Object.setPrototypeOf(this, HttpClientException.prototype) | ||
Error.captureStackTrace(this, HttpClientException) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './error' | ||
export * from './server' | ||
export * from './auth' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import axios from 'axios' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. axios 말고 node-fetch 사용할게요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 이유를 여쭤봐도 될까요? 저 또한 axios 와 node-fetch를 고민했어서 의견을 구하고 싶습니다 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 큰 차이는 없습니다. |
||
import { HttpClientException } from '@/types/errors' | ||
import { User } from '@/models/user' | ||
import jwt from 'jsonwebtoken' | ||
import { JWT_PRIVATE_KEY } from '@/config' | ||
|
||
export interface OAuthPayload { | ||
accessToken: string | ||
provider: string | ||
} | ||
|
||
export interface IUserInfo { | ||
nickname: string | ||
provider: string | ||
providerId: string | ||
} | ||
|
||
export class Kakao { | ||
public static async getUserInfo(payload: OAuthPayload) { | ||
const { data } = await axios('https://kapi.kakao.com/v2/user/me', { | ||
method: 'GET', | ||
headers: { | ||
Authorization: `Bearer ${payload.accessToken}`, | ||
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', | ||
}, | ||
}).catch(e => { | ||
throw new HttpClientException(e) | ||
}) | ||
|
||
return { | ||
nickname: data.kakao_account.profile.nickname, | ||
provider: 'KAKAO', | ||
providerId: data.id, | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oauth provider 코드는 각 Provider 별 파일로 관리하는게 좋을것 같아요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 추후에 provider 추가시에 해당 부분도 같이 작업 진행해보겠습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추후 provier 작업을 다른 사람이 할수도 있으니 지금 하는게 좋을거 같아요 |
||
|
||
export class JWTProvider { | ||
public static issueJwt(user: User): string { | ||
return jwt.sign({ issuer: 'FIENMEE', user: user }, JWT_PRIVATE_KEY, { expiresIn: '30m' }) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types 는 dev dependencies 로 넣어주시면 됩니다