Skip to content

Commit f7f8f8f

Browse files
committed
Merge branch 'dev' of https://github.com/boostcampwm-2022/web27-Wabinar into test/#24-K
2 parents e527e74 + d2723c8 commit f7f8f8f

File tree

8 files changed

+171
-46
lines changed

8 files changed

+171
-46
lines changed

server/.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@typescript-eslint/explicit-module-boundary-types": "off",
1818
"@typescript-eslint/no-explicit-any": "off",
1919
"@typescript-eslint/ban-types": "off",
20+
"@typescript-eslint/no-var-requires": "off",
2021
"no-nested-ternary": "error",
2122
"eqeqeq": "error",
2223
"no-console": "warn"

server/apis/auth/service.github.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import axios from 'axios';
2+
import env from '@config';
3+
import AuthorizationError from '@errors/authorization-error';
4+
5+
const ACCESS_TOKEN_REQUEST_URL = 'https://github.com/login/oauth/access_token';
6+
const USER_REQUEST_URL = 'https://api.github.com/user';
7+
8+
export const getAccessToken = async (code: string) => {
9+
const body = {
10+
client_id: env.GITHUB_CLIENT_ID,
11+
client_secret: env.GITHUB_CLIENT_SECRET,
12+
code,
13+
};
14+
const headers = {
15+
'Content-Type': 'application/json',
16+
Accept: 'application/json',
17+
};
18+
19+
const { data: accessTokenResponse } = await axios.post(
20+
ACCESS_TOKEN_REQUEST_URL,
21+
body,
22+
{
23+
headers,
24+
},
25+
);
26+
27+
if (accessTokenResponse.error) {
28+
throw new Error('access token 생성 요청 실패');
29+
}
30+
31+
return accessTokenResponse;
32+
};
33+
34+
export const getGithubUser = async (accessToken: string, tokenType: string) => {
35+
const { data: user } = await axios.get(USER_REQUEST_URL, {
36+
headers: {
37+
Authorization: `${tokenType} ${accessToken}`,
38+
},
39+
});
40+
41+
if (user.error) {
42+
throw new AuthorizationError('OAuth 유저 정보 요청 실패');
43+
}
44+
45+
return user;
46+
};

server/apis/auth/service.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const { login } = require('./service');
2+
const { getAccessToken, getGithubUser } = require('./service.github');
3+
const userModel = require('@apis/user/model');
4+
const jwt = require('@utils/jwt');
5+
6+
jest.mock('./service.github', () => {
7+
return {
8+
getAccessToken: jest.fn(),
9+
getGithubUser: jest.fn(),
10+
};
11+
});
12+
13+
jest.mock('@apis/user/model', () => {
14+
return {
15+
exists: jest.fn(),
16+
create: jest.fn().mockImplementation(() => undefined),
17+
};
18+
});
19+
20+
jest.mock('@utils/jwt', () => {
21+
return {
22+
generateAccessToken: jest.fn(),
23+
generateRefreshToken: jest.fn(),
24+
};
25+
});
26+
27+
describe('login', () => {
28+
const successfulTokenResponse = {
29+
access_token: '',
30+
token_type: '',
31+
};
32+
const successfulUserResponse = {
33+
id: '',
34+
login: '',
35+
avatar_url: '',
36+
};
37+
38+
const ACCESS_TOKEN = 'ACCESS_TOKEN';
39+
const REFRESH_TOKEN = 'REFRESH_TOKEN';
40+
41+
jwt.generateAccessToken.mockReturnValue(ACCESS_TOKEN);
42+
jwt.generateRefreshToken.mockReturnValue(REFRESH_TOKEN);
43+
44+
it('유효한 코드를 받으면 토큰이 성공적으로 생성된다.', async () => {
45+
getAccessToken.mockResolvedValueOnce(successfulTokenResponse);
46+
getGithubUser.mockResolvedValueOnce(successfulUserResponse);
47+
userModel.exists.mockReturnValue(false);
48+
49+
const { loginToken, refreshToken } = await login('');
50+
51+
expect(loginToken).toBe(ACCESS_TOKEN);
52+
expect(refreshToken).toBe(REFRESH_TOKEN);
53+
});
54+
55+
it('엑세스 토큰 획득 실패 시 에러를 던진다.', async () => {
56+
getAccessToken.mockRejectedValueOnce(new Error('fail'));
57+
58+
expect(() => login()).rejects.toThrow('fail');
59+
});
60+
61+
it('유저 정보 획득 실패 시 에러를 던진다.', async () => {
62+
getAccessToken.mockResolvedValueOnce(successfulTokenResponse);
63+
getGithubUser.mockRejectedValueOnce(new Error('fail'));
64+
65+
expect(() => login()).rejects.toThrow('fail');
66+
});
67+
});

server/apis/auth/service.ts

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,12 @@
1-
import axios from 'axios';
2-
import env from '@config';
31
import userModel from '@apis/user/model';
42
import * as jwt from '@utils/jwt';
5-
import AuthorizationError from '@errors/authorization-error';
3+
import { getAccessToken, getGithubUser } from './service.github';
64

75
interface TokenResponse {
86
access_token: string;
97
token_type: string;
108
}
119

12-
const ACCESS_TOKEN_REQUEST_URL = 'https://github.com/login/oauth/access_token';
13-
const USER_REQUEST_URL = 'https://api.github.com/user';
14-
15-
const getAccessToken = async (code: string) => {
16-
const body = {
17-
client_id: env.GITHUB_CLIENT_ID,
18-
client_secret: env.GITHUB_CLIENT_SECRET,
19-
code,
20-
};
21-
const headers = {
22-
'Content-Type': 'application/json',
23-
Accept: 'application/json',
24-
};
25-
26-
const { data: accessTokenResponse } = await axios.post(
27-
ACCESS_TOKEN_REQUEST_URL,
28-
body,
29-
{
30-
headers,
31-
},
32-
);
33-
34-
if (accessTokenResponse.error) {
35-
throw new AuthorizationError('access token 생성 요청 실패');
36-
}
37-
38-
return accessTokenResponse;
39-
};
40-
41-
const getGithubUser = async (accessToken: string, tokenType: string) => {
42-
const { data: user } = await axios.get(USER_REQUEST_URL, {
43-
headers: {
44-
Authorization: `${tokenType} ${accessToken}`,
45-
},
46-
});
47-
48-
if (user.error) {
49-
throw new AuthorizationError('OAuth 유저 정보 요청 실패');
50-
}
51-
52-
return user;
53-
};
54-
5510
export const login = async (code: string) => {
5611
const { access_token: accessToken, token_type: tokenType }: TokenResponse =
5712
await getAccessToken(code);

server/apis/user/controller.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import express, { Request, Response, NextFunction } from 'express';
2+
import asyncWrapper from '@utils/async-wrapper';
3+
import * as userService from './service';
4+
import jwtAuthenticator from '@middlewares/jwt-authenticator';
5+
6+
const router = express.Router();
7+
8+
router.get(
9+
'/:id/workspace',
10+
jwtAuthenticator,
11+
asyncWrapper(async (req: Request, res: Response, next: NextFunction) => {
12+
const { id: userId } = req.user;
13+
const { id: targetUserId } = req.params;
14+
15+
const workspaces = await userService.getWorkspaces(
16+
Number(targetUserId),
17+
userId,
18+
);
19+
20+
res.send({ workspaces });
21+
}),
22+
);
23+
24+
export default router;

server/apis/user/service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import userModel from '@apis/user/model';
2+
import workspaceModel from '@apis/workspace/model';
3+
import AuthorizationError from '@errors/authorization-error';
4+
5+
export const getWorkspaces = async (targetUserId: number, userId: number) => {
6+
if (targetUserId !== userId)
7+
throw new AuthorizationError(
8+
'요청하신 유저 정보와 현재 로그인된 유저 정보가 달라요 ^^',
9+
);
10+
11+
const user = await userModel.findOne({ id: targetUserId });
12+
13+
const workspaces = await workspaceModel.find({
14+
id: { $in: user.workspaces },
15+
});
16+
17+
return workspaces;
18+
};

server/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import cookieParser from 'cookie-parser';
33
import env from '@config';
44
import authRouter from '@apis/auth/controller';
55
import workspaceRouter from '@apis/workspace/controller';
6+
import userRouter from '@apis/user/controller';
67
import errorHandler from '@middlewares/error-handler';
78

89
const app = express();
@@ -12,6 +13,7 @@ app.use(cookieParser(env.COOKIE_SECRET_KEY));
1213
app.get('/', (req: Request, res: Response) => res.send('Express'));
1314
app.use('/auth', authRouter);
1415
app.use('/workspace', workspaceRouter);
16+
app.use('/user', userRouter);
1517

1618
app.use(errorHandler);
1719

server/jest.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
module.exports = {
2+
preset: 'ts-jest', // to use typescript
3+
verbose: true,
4+
moduleNameMapper: {
5+
'@apis/(.*)': '<rootDir>/apis/$1',
6+
'@config': '<rootDir>/config',
7+
'@constants/(.*)': '<rootDir>/constants/$1',
8+
'@db': '<rootDir>/db',
9+
'@middlewares/(.*)': '<rootDir>/middlewares/$1',
10+
'@utils/(.*)': '<rootDir>/utils/$1',
11+
},
12+
};
113
module.exports = {
214
preset: 'ts-jest', // to use typescript
315
verbose: true,

0 commit comments

Comments
 (0)