Skip to content

Commit

Permalink
fix: resolve conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
yuchem2 committed Dec 15, 2024
2 parents 198f280 + 23ea8d7 commit b9f8fdf
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 74 deletions.
3 changes: 3 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
"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",
"node-schedule": "^2.1.1",
"node-fetch": "^3.3.2",
"winston": "^3.16.0",
"winston-daily-rotate-file": "^5.0.0"
},
Expand All @@ -39,6 +41,7 @@
"@types/hpp": "^0.2.6",
"@types/jest": "^29.5.14",
"@types/node-schedule": "^2.1.7",
"@types/jsonwebtoken": "^9.0.7",
"nodemon": "^3.1.7",
"swc-node": "^1.0.0"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { config } from 'dotenv'

config()

export const { NODE_ENV, DB_URI, DB_NAME, SEOUL_API_KEY, SEOUL_API_URL } = process.env
export const { NODE_ENV, DB_URI, DB_NAME, JWT_SECRET, SEOUL_API_KEY, SEOUL_API_URL } = process.env

export const PORT = Number.parseInt(process.env.PORT) || 5000
2 changes: 2 additions & 0 deletions apps/server/src/controllers/v1/index.ts
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,
}
49 changes: 49 additions & 0 deletions apps/server/src/controllers/v1/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import asyncify from 'express-asyncify'
import express, { Request, Response } from 'express'
import { IUserInfo, JWTProvider, OAuthPayload } from '@/utils/auth'
import { User, UserModel } from '@/models/user'
import Kakao from '@/types/provider/kakao'
import { KakaoLoginFailedException } from '@/types/errors'

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') {
const userInfo: IUserInfo = await Kakao.getUserInfo(oauthPayload)
const user: User = await UserModel.findByProviderId(userInfo.providerId)
if (!user) throw new KakaoLoginFailedException(new Error(`not found ${userInfo.providerId}`))
const jwt = JWTProvider.issueJwt(user)
await UserModel.setProviderCredentials({
providerId: userInfo.providerId,
providerAccessToken: oauthPayload.accessToken,
providerRefreshToken: oauthPayload.refreshToken,
})
res.status(200).json({
accessToken: jwt,
})
}
})

router.post('/register', async (req: Request, res: Response) => {
const oauthRegisterPayload: OAuthPayload = req.body

if (oauthRegisterPayload.provider === 'KAKAO') {
const registerInfo: IUserInfo = await Kakao.getUserInfo(oauthRegisterPayload)
const user: User = await UserModel.createUser(registerInfo)
const jwt = JWTProvider.issueJwt(user)
await UserModel.setProviderCredentials({
providerId: registerInfo.providerId,
providerAccessToken: oauthRegisterPayload.accessToken,
providerRefreshToken: oauthRegisterPayload.refreshToken,
})
res.status(200).json({
accessToken: jwt,
})
}
})

export default router
32 changes: 31 additions & 1 deletion apps/server/src/models/user.ts
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 { IUserCredentials, IUserInfo } from '@/utils/auth'

export class User extends TimeStamps {
public _id: mongoose.Types.ObjectId
Expand All @@ -13,6 +14,35 @@ export class User extends TimeStamps {

@prop()
public providerId: string

@prop()
public providerAccessToken: string

@prop()
public providerRefreshToken: string

public toJSON() {
return {
_id: this._id,
nickname: this.nickname,
provider: this.provider,
}
}

public static async findByProviderId(this: ReturnModelType<typeof User>, providerId: string) {
return await this.findOne({ providerId: providerId }).exec()
}

public static async createUser(this: ReturnModelType<typeof User>, info: IUserInfo) {
return await this.create(info)
}

public static async setProviderCredentials(this: ReturnModelType<typeof User>, credentials: IUserCredentials) {
return await this.findOneAndUpdate(
{ providerId: credentials.providerId },
{ providerAccessToken: credentials.providerAccessToken, providerRefreshToken: credentials.providerRefreshToken },
).exec()
}
}

export const UserModel = getModelForClass(User)
1 change: 1 addition & 0 deletions apps/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default class Server {
this.app.use('/v1/enquiries', controllers.v1.enquiries)
this.app.use('/v1/events', controllers.v1.events)
this.app.use('/v1/schedules', controllers.v1.schedules)
this.app.use('/v1/user', controllers.v1.user)
}

setPostMiddleware() {
Expand Down
25 changes: 25 additions & 0 deletions apps/server/src/types/errors/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { APIError } from '@/types/errors/error'

export class OAuthUserInfoException extends APIError {
constructor(cause: Error | string = null) {
super(422, 4220, 'auth http client exception', cause)
Object.setPrototypeOf(this, OAuthUserInfoException.prototype)
Error.captureStackTrace(this, OAuthUserInfoException)
}
}

export class KakaoLoginFailedException extends APIError {
constructor(cause: Error | string = null) {
super(404, 4040, 'Failed to login with kakao login information', cause)
Object.setPrototypeOf(this, KakaoLoginFailedException.prototype)
Error.captureStackTrace(this, KakaoLoginFailedException)
}
}

export class KakaoRegisterFailedException extends APIError {
constructor(cause: Error | string = null) {
super(400, 4000, 'Failed to register user information', cause)
Object.setPrototypeOf(this, KakaoRegisterFailedException)
Error.captureStackTrace(this, KakaoRegisterFailedException)
}
}
1 change: 1 addition & 0 deletions apps/server/src/types/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './error'
export * from './server'
export * from './schedule'
export * from './databatch'
export * from './auth'
27 changes: 27 additions & 0 deletions apps/server/src/types/provider/kakao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { OAuthUserInfoException } from '@/types/errors'
import { OAuthPayload } from '@/utils/auth'
import fetch from 'node-fetch'

export class Kakao {
public static async getUserInfo(payload: OAuthPayload) {
const response = await fetch('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 OAuthUserInfoException(e)
})

const data = await response.json()

return {
nickname: data.kakao_account.profile.nickname,
provider: 'KAKAO',
providerId: data.id,
}
}
}

export default Kakao
27 changes: 27 additions & 0 deletions apps/server/src/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { User } from '@/models/user'
import jwt from 'jsonwebtoken'
import { JWT_SECRET } from '@/config'

export interface OAuthPayload {
accessToken: string
refreshToken: string
provider: string
}

export interface IUserInfo {
nickname: string
provider: string
providerId: string
}

export interface IUserCredentials {
providerId: string
providerAccessToken: string
providerRefreshToken: string
}

export class JWTProvider {
public static issueJwt(user: User): string {
return jwt.sign({ issuer: 'FIENMEE', user: user }, JWT_SECRET, { expiresIn: '30m' })
}
}
Loading

0 comments on commit b9f8fdf

Please sign in to comment.