Skip to content

Commit 1f7f261

Browse files
authored
Feat/#369-K: API 요청에 대한 권한 확인 (#370)
* fix: 인증 API 응답에서 workspaces 제거 * fix: 주석 처리했던 getWorkspaces 권한 확인 로직 제거 * feat: 특정 워크스페이스의 정보 받아오는 API에 권한 확인 로직 추가 * test: workspace service 테스트에 변경사항 반영 * fix: USER_ID 상수 사용
1 parent cfde378 commit 1f7f261

File tree

12 files changed

+81
-57
lines changed

12 files changed

+81
-57
lines changed

@wabinar/api-types/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import { User } from './user';
2+
13
export interface PostLoginBody {
24
code: string;
35
}
6+
7+
export interface LoginResBody {
8+
user: User;
9+
}

@wabinar/api-types/user.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { Workspace } from './workspace';
22

3+
export type User = {
4+
id: number;
5+
name: string;
6+
avatarUrl: string;
7+
};
8+
39
export interface GetWorkspaceParams {
410
id: number;
511
}

client/src/apis/auth.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
import { PostLoginBody } from '@wabinar/api-types/auth';
2-
import { User } from 'src/types/user';
3-
import { Workspace } from 'src/types/workspace';
1+
import { LoginResBody, PostLoginBody } from '@wabinar/api-types/auth';
42

53
import { http } from './http';
64
import { CREATED, OK } from './http-status';
75

8-
// TODO: BE API 변경할 때 제거
9-
type GetUserInfo = {
10-
user: User;
11-
workspaces: Workspace[];
12-
};
13-
14-
export const getAuth = async (): Promise<GetUserInfo> => {
6+
export const getAuth = async (): Promise<LoginResBody> => {
157
const res = await http.get(`/auth`);
168

179
if (res.status !== OK) throw new Error();
@@ -21,7 +13,7 @@ export const getAuth = async (): Promise<GetUserInfo> => {
2113

2214
export const postAuthLogin = async ({
2315
code,
24-
}: PostLoginBody): Promise<GetUserInfo> => {
16+
}: PostLoginBody): Promise<LoginResBody> => {
2517
const res = await http.post(`/auth/login`, { code });
2618

2719
if (res.status !== CREATED) throw new Error();

server/apis/auth/controller.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { CREATED, OK } from '@constants/http-status';
22
import jwtAuthenticator from '@middlewares/jwt-authenticator';
3-
import { PostLoginBody } from '@wabinar/api-types/auth';
3+
import { LoginResBody, PostLoginBody } from '@wabinar/api-types/auth';
44
import asyncWrapper from '@utils/async-wrapper';
55
import express, { Request, Response } from 'express';
6-
import * as userService from '../user/service';
76
import * as authService from './service';
87

98
interface CookieOptions {
@@ -18,47 +17,44 @@ const router = express.Router();
1817
router.get(
1918
'/',
2019
jwtAuthenticator,
21-
asyncWrapper(async (req: Request, res: Response) => {
20+
asyncWrapper(async (req: Request, res: Response<LoginResBody>) => {
2221
if (!req.user) {
2322
res.status(OK).send({ user: req.user });
2423
return;
2524
}
2625

27-
// req.user가 존재하면 workspaceList를 같이 받아옴
2826
const { id: userId, name, avatarUrl } = req.user;
2927

30-
const workspaces = await userService.getWorkspaces(userId);
31-
3228
const user = { id: userId, name, avatarUrl };
3329

34-
res.status(OK).send({ user, workspaces });
30+
res.status(OK).send({ user });
3531
}),
3632
);
3733

38-
// authorized된 유저가 아닐 경우(위에서 !req.user조건으로 return 됨)
39-
// OAuth 페이지에서 workspace로 이동(navigate)하게 되므로
40-
// 로그인 하여 받아온 유저 정보로 workspace list 정보도 함께 받아온다.
4134
router.post(
4235
'/login',
4336
jwtAuthenticator,
44-
asyncWrapper(async (req: Request<{}, {}, PostLoginBody>, res: Response) => {
45-
const { code } = req.body;
37+
asyncWrapper(
38+
async (
39+
req: Request<{}, {}, PostLoginBody>,
40+
res: Response<LoginResBody>,
41+
) => {
42+
const { code } = req.body;
4643

47-
const { user, loginToken, refreshToken } = await authService.login(code);
44+
const { user, loginToken, refreshToken } = await authService.login(code);
4845

49-
const workspaces = await userService.getWorkspaces(Number(user.id));
46+
const cookieOptions: CookieOptions = {
47+
httpOnly: true,
48+
sameSite: 'lax',
49+
maxAge: 1000 * 60 * 60 * 24,
50+
signed: true,
51+
};
52+
res.cookie('accessToken', loginToken, cookieOptions);
53+
res.cookie('refreshToken', refreshToken, cookieOptions);
5054

51-
const cookieOptions: CookieOptions = {
52-
httpOnly: true,
53-
sameSite: 'lax',
54-
maxAge: 1000 * 60 * 60 * 24,
55-
signed: true,
56-
};
57-
res.cookie('accessToken', loginToken, cookieOptions);
58-
res.cookie('refreshToken', refreshToken, cookieOptions);
59-
60-
res.status(CREATED).send({ user, workspaces });
61-
}),
55+
res.status(CREATED).send({ user });
56+
},
57+
),
6258
);
6359

6460
router.delete(

server/apis/user/service.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import ForbiddenError from '@errors/forbidden-error';
2+
13
const { getWorkspaces } = require('./service');
24
const userModel = require('@apis/user/model');
35
const workspaceModel = require('@apis/workspace/model');
4-
const { default: AuthorizationError } = require('@errors/authorization-error');
56

67
jest.mock('@apis/user/model', () => {
78
return { findOne: jest.fn() };
@@ -24,16 +25,12 @@ describe('getWorkspaces', () => {
2425
expect(workspaces).toEqual(successfulWorkspaces);
2526
});
2627

27-
/*
2828
it('비정상적인 유저 아이디를 받으면 에러를 던진다.', async () => {
2929
const user1 = 1;
3030
const user2 = 2;
3131

32-
expect(() => getWorkspaces(user1, user2)).rejects.toThrow(
33-
AuthorizationError,
34-
);
32+
expect(() => getWorkspaces(user1, user2)).rejects.toThrow(ForbiddenError);
3533
});
36-
*/
3734
});
3835

3936
export {};

server/apis/user/service.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import userModel from '@apis/user/model';
22
import workspaceModel from '@apis/workspace/model';
3+
import ERROR_MESSAGE from '@constants/error-message';
4+
import ForbiddenError from '@errors/forbidden-error';
35

46
export const getWorkspaces = async (userId: number, targetUserId?: number) => {
5-
/*
67
if (targetUserId !== userId) {
7-
throw new AuthorizationError(
8-
'요청하신 유저 정보와 현재 로그인된 유저 정보가 달라요 ^^',
9-
);
8+
throw new ForbiddenError(ERROR_MESSAGE.FORBIDDEN_WORKSPACES);
109
}
11-
*/
1210

1311
const user = await userModel.findOne({ id: userId });
1412

server/apis/workspace/controller.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ router.get(
4343
asyncWrapper(async (req: Request<GetInfoParams>, res: Response) => {
4444
const { id: workspaceId } = req.params;
4545

46-
const workspaceInfo = await workspaceService.info(Number(workspaceId));
46+
const workspaceInfo = await workspaceService.info(
47+
req.user.id,
48+
Number(workspaceId),
49+
);
4750

4851
res.status(OK).send(workspaceInfo);
4952
}),

server/apis/workspace/service.test.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const workspaceModel = require('./model');
22
const workspaceService = require('./service');
3+
const userModel = require('@apis/user/model');
34
const { default: InvalidJoinError } = require('@errors/invalid-join-error');
45
const {
56
default: InvalidWorkspaceError,
@@ -118,47 +119,53 @@ describe('join', () => {
118119
});
119120

120121
describe('info', () => {
122+
const USER_ID = 1;
121123
const WORKSPACE_ID = 1;
122124
const INVALID_WORKSPACE_ID = -1;
123125

124126
it('워크스페이스 ID가 DB에 존재할 경우 조회에 성공한다.', async () => {
125127
const WORKSPACE_NAME = 'Wab';
128+
const USER = { id: 1, name: 'name', avatarUrl: 'avatarUrl' };
126129

127130
workspaceModel.findOne.mockResolvedValueOnce({
128131
id: WORKSPACE_ID,
129132
name: WORKSPACE_NAME,
130133
code: VALID_CODE,
131-
users: [],
134+
users: [USER_ID],
132135
moms: [],
133136
});
134137

135-
expect(workspaceService.info(WORKSPACE_ID)).resolves.toEqual({
138+
userModel.find.mockResolvedValueOnce([USER]);
139+
140+
expect(workspaceService.info(USER_ID, WORKSPACE_ID)).resolves.toEqual({
136141
name: WORKSPACE_NAME,
137-
members: [],
142+
members: [USER],
138143
moms: [],
139144
});
140145
});
141146

142147
it('워크스페이스 Id가 없는 경우 실패한다.', async () => {
143-
expect(() => workspaceService.info()).rejects.toThrow(
148+
expect(() => workspaceService.info(USER_ID)).rejects.toThrow(
144149
InvalidWorkspaceError,
145150
);
146151
});
147152

148153
it('워크스페이스 Id가 DB에 존재하지 않는 경우 실패한다.', async () => {
149154
workspaceModel.findOne.mockResolvedValueOnce(null);
150155

151-
expect(() => workspaceService.info(INVALID_WORKSPACE_ID)).rejects.toThrow(
152-
InvalidWorkspaceError,
153-
);
156+
expect(() =>
157+
workspaceService.info(USER_ID, INVALID_WORKSPACE_ID),
158+
).rejects.toThrow(InvalidWorkspaceError);
154159
});
155160

156161
it('워크스페이스 정보 획득 실패 시 에러를 던진다.', async () => {
157162
workspaceModel.findOne.mockRejectedValueOnce(
158163
new Error('Some error in database operation'),
159164
);
160165

161-
expect(() => workspaceService.info(WORKSPACE_ID)).rejects.toThrow();
166+
expect(() =>
167+
workspaceService.info(USER_ID, WORKSPACE_ID),
168+
).rejects.toThrow();
162169
});
163170
});
164171

server/apis/workspace/service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import momModel from '@apis/mom/model';
22
import userModel, { User } from '@apis/user/model';
33
import ERROR_MESSAGE from '@constants/error-message';
44
import AuthorizationError from '@errors/authorization-error';
5+
import ForbiddenError from '@errors/forbidden-error';
56
import InvalidJoinError from '@errors/invalid-join-error';
67
import InvalidWorkspaceError from '@errors/invalid-workspace-error';
78
import { v4 as uuidv4 } from 'uuid';
@@ -43,7 +44,9 @@ export const join = async (userId: number, code: string) => {
4344
return { id, name, code };
4445
};
4546

46-
export const info = async (workspaceId: number) => {
47+
export const info = async (userId: number, workspaceId: number) => {
48+
if (!userId) throw new AuthorizationError(ERROR_MESSAGE.UNAUTHORIZED);
49+
4750
if (!workspaceId) throw new InvalidWorkspaceError(ERROR_MESSAGE.BAD_REQUEST);
4851

4952
const workspace = await workspaceModel.findOne({ id: workspaceId });
@@ -61,6 +64,9 @@ export const info = async (workspaceId: number) => {
6164
{ id: 1, name: 1, avatarUrl: 1, _id: 0 },
6265
)) || [];
6366

67+
if (!members.filter((member) => member.id === userId).length)
68+
throw new ForbiddenError(ERROR_MESSAGE.FORBIDDEN);
69+
6470
const moms: string[] = momsIds.length
6571
? await momModel.find({ id: { $in: momsIds } }, { title: 1 })
6672
: [];

server/constants/error-message.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ const ERROR_MESSAGE = {
22
UNAUTHORIZED: '유저 인증 실패',
33
UNAUTHORIZED_OAUTH: 'OAuth 유저 인증 실패',
44
ACCESS_TOKEN_REQUEST_FAILED: 'access 토큰 요청 실패',
5+
FORBIDDEN: '접근 권한이 없으세요 ^^',
56

7+
FORBIDDEN_WORKSPACES: '요청하신 유저 정보가 로그인 정보와 달라요 ^^',
68
ALREADY_JOINED_WORKSPACE: '이미 참여한 워크스페이스',
79
INVALID_WORKSPACE: '존재하지 않는 워크스페이스',
810

0 commit comments

Comments
 (0)