[7주차/찬찬] 워크북 제출합니다#12
Conversation
feat: 7주차 Tsoa 및 미들웨어 적용
| export class AppError extends Error { | ||
| public readonly errorCode: string; | ||
| public readonly statusCode: number; | ||
| public readonly data?: unknown; | ||
|
|
||
| constructor(params: { | ||
| errorCode: string; | ||
| message: string; | ||
| statusCode: number; | ||
| data?: unknown; | ||
| }) { | ||
| super(params.message); | ||
|
|
||
| this.errorCode = params.errorCode; | ||
| this.statusCode = params.statusCode; | ||
| this.data = params.data ?? null; | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
전역 AppError 선언해서 사용하신것 좋습니다~
|
|
||
| export function authorizeUser() { | ||
| return async (req: Request, res: Response, next: NextFunction) => { | ||
| const { username } = req.cookies; |
There was a problem hiding this comment.
클라이언트에서 보내준 쿠키만 검증에 사용하는것은 위험할 수 있습니다! 항상 서버는 클라이언트를 의심하고 보수적으로 동작해야한다는것을 명심해주세요!
| if (username) { | ||
| console.log(`[인증 성공] ${username}님, 환영합니다.`); | ||
| next(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
username은 클라이언트가 조작할 수 있습니다.
인증에는 최소한 다음 중 하나가 필요합니다.
- 서버 세션 ID
- 서명된 쿠키
- JWT access token
- DB에서 검증 가능한 session token
단순히 username 문자열만 보고 인증하면 안 됩니다!
| res | ||
| .status(401) | ||
| .send( | ||
| '<script>alert("로그인이 필요합니다!");location.href="/api/v1/users/login";</script>' | ||
| ); |
There was a problem hiding this comment.
응답을 HTML script로 하는것은 의도한 대로 동작하지 않을수 있습니다! API 서버라면 가능하면 응답형식을 JSON으로 통일하는 방법으로 고려하고 alert 창이 뜨는 로직은 클라이언트에 위임하는 방식으로 생각해주세요!
| */ | ||
| const router = express.Router(); | ||
|
|
||
| app.get("/api/v1/users/:userId/reviews", handleListMyReviews); | ||
| RegisterRoutes(router); |
There was a problem hiding this comment.
코드를 확인해보니 user이외에는 tsoa 등록이 안되어있네요! tsoa는 @ decorator를 통해서만 경로가 등록되니 추후에 나머지 엔드포인트도 수정해주세요!
| @Get("guest") | ||
| public async handleGuestPage(): Promise<string> { | ||
| return ` | ||
| <h1>게스트 페이지</h1> | ||
| <p>이 페이지는 로그인이 필요 없습니다.</p> | ||
| <ul> | ||
| <li><a href="/api/v1/users/mypage">마이페이지로 이동</a></li> | ||
| </ul> | ||
| `; | ||
| } | ||
|
|
||
| @Get("login") | ||
| public async handleLoginPage(): Promise<string> { | ||
| return ` | ||
| <h1>로그인 페이지</h1> | ||
| <p>로그인이 필요한 페이지에서 이동합니다.</p> | ||
| <a href="/api/v1/users/set-login">로그인 쿠키 생성하기</a> | ||
| `; | ||
| } | ||
|
|
||
| res.status(StatusCodes.OK).json({ | ||
| result: user, | ||
| @Get("mypage") | ||
| @Middlewares(authorizeUser()) | ||
| public async handleMypage( | ||
| @Request() req: ExpressRequest | ||
| ): Promise<string> { | ||
| return ` | ||
| <h1>마이페이지</h1> | ||
| <p>환영합니다, ${req.cookies.username}님!</p> | ||
| <p>이 페이지는 로그인한 사람만 볼 수 있습니다.</p> | ||
| <a href="/api/v1/users/set-logout">로그아웃</a> | ||
| `; | ||
| } |
There was a problem hiding this comment.
단순 페이지 반환, 비동기 로직이 없는 코드는 async를 안붙이셔도 됩니다! 로직 내부에서 repo나 비동기 처리가 필요한 함수만 async 를 명시해주세요
| export const userSignUp = async ( | ||
| data: UserSignUpRequest | ||
| ): Promise<UserSignUpResponse> => { | ||
| const joinUserId = await addUser({ | ||
| email: data.email, | ||
| password: hashedPassword, | ||
| password: data.password, | ||
| name: data.name, | ||
| gender: data.gender, | ||
| birth: data.birth, | ||
| address: data.address, | ||
| detailAddress: data.detailAddress, | ||
|
|
||
| // 요청 body에서는 birth가 문자열로 들어오기 때문에 Date 객체로 변환합니다. | ||
| birth: new Date(data.birth), | ||
|
|
||
| // 선택값은 undefined로 들어올 수 있으므로 기본값을 넣어줍니다. | ||
| address: data.address ?? "", | ||
| detailAddress: data.detailAddress ?? "", | ||
| phoneNumber: data.phoneNumber, | ||
| }); |
There was a problem hiding this comment.
Oauth를 통해 회원가입, 로그인 하는 경우 각자 필요한 데이터 필드가 다릅니다! 단순 이메일로 회원가입 로그인도 처리 될 수 있도록 확인해주세요!
There was a problem hiding this comment.
Oauth 회원가입이 들어가는 경우 원래 사용하던 user schema와 필요한 데이터가 각자 다를 수 있습니다! 단일 schema로 운영할 경우 해당 유저가 어떤 oauth로 회원가입했는지 확인하는 필드, 각 oauth에서 발급해주는 id 등을 추가해주시면 좋을거 같습니다!
| export interface ApiResponse<T> { | ||
| resultType: "SUCCESS"; | ||
| error: null; | ||
| success: T; | ||
| } | ||
|
|
||
| export const success = <T>(data: T): ApiResponse<T> => ({ | ||
| resultType: "SUCCESS", | ||
| error: null, | ||
| success: data, | ||
| }); No newline at end of file |
✅ 실습 체크리스트
✅ 컨벤션 체크리스트