diff --git a/src/controller/user.controller.ts b/src/controller/user.controller.ts index 77a01d3..c950637 100644 --- a/src/controller/user.controller.ts +++ b/src/controller/user.controller.ts @@ -13,6 +13,15 @@ class UserController { res.json(users); }; + createAdmin = async (req: Request, res: Response, next: NextFunction) => { + logger.info('UserController: createAdmin called'); + + const email = req.body.email; + const password = req.body.password; + const user = await this.userService.createAdmin(email, password); + res.json(user); + }; + createUser = async (req: Request, res: Response, next: NextFunction) => { logger.info('UserController: createUser called'); diff --git a/src/repository/user.repository.ts b/src/repository/user.repository.ts index adc787a..e772414 100644 --- a/src/repository/user.repository.ts +++ b/src/repository/user.repository.ts @@ -50,6 +50,14 @@ class UserRepository { OrganizationId: organizationId, }); } + + async createAdmin(email: string, password: string) { + return User.create({ + email, + password, + role: 'admin', + }); + } } export { UserRepository }; diff --git a/src/routes/user.ts b/src/routes/user.ts index 79d6625..d4839e6 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -13,6 +13,13 @@ export function createUsersRouter() { wrapAsync(userController.findAll), ); + // 관리자 계정 생성 + router.post( + '/admin', + wrapAsync(validateIsAdmin), + wrapAsync(userController.createAdmin), + ); + // 계정 생성 // TODO: email sanitize (kaist email만 가능하도록) router.post('/', wrapAsync(userController.createUser)); diff --git a/src/service/user.service.ts b/src/service/user.service.ts index 4c70c7d..269d4e1 100644 --- a/src/service/user.service.ts +++ b/src/service/user.service.ts @@ -59,6 +59,24 @@ class UserService { return userReponse; } + async createAdmin(email: string, password: string) { + const user = await this.userRepository.findByEmail(email); + if (user) { + throw new DuplicateError('이미 등록된 이메일이 존재합니다.'); + } + + const encryptedPassword = await this.passwordService.encrypt(password); + const admin = await this.userRepository.createAdmin( + email, + encryptedPassword, + ); + logger.info(`Admin: ${email} created`); + return { + email: admin.email, + role: admin.role, + }; + } + async create(dto: CreateUserDto) { await this.checkDuplicate(dto); @@ -111,11 +129,21 @@ class UserService { async login(dto: LoginDto) { const user = await this.findUserByEmailOrThrow(dto.email); + if (user.isDisabled) { + throw new UnauthorizedError('비활성화된 계정입니다.'); + } await this.matchPassword(dto.password, user.password); logger.info(`User: ${dto.email} logged in`); + if (user.role === 'admin') { + return { + id: user.id, + email: user.email, + role: user.role, + }; + } const organization = await this.findOrganizationByIdOrThrow( user.OrganizationId, ); diff --git a/swagger/routes/user.yaml b/swagger/routes/user.yaml index c118627..667c097 100644 --- a/swagger/routes/user.yaml +++ b/swagger/routes/user.yaml @@ -71,6 +71,45 @@ paths: description: BadRequestError. 비밀번호는 8자 이상 12자 이하여야 합니다. '409': description: ValidationError. 이미 존재하는 이메일입니다. + /users/admin: + post: + description: 관리자 계정 생성 (관리자만 호출 가능) + tags: + - 계정 + requestBody: + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: 이메일 + example: admin@kaist.ac.kr + password: + type: string + description: 비밀번호 + example: password + required: + - email + - password + responses: + '200': + content: + application/json: + schema: + type: object + properties: + email: + type: string + description: 이메일 + example: admin@kaist.ac.kr + role: + type: string + description: 권한 + example: admin + '409': + description: DuplicateError. 이미 존재하는 이메일입니다. /users/login: post: description: 로그인. 세션 생성. diff --git a/test/mock/user.ts b/test/mock/user.ts index 70828ad..9457a4d 100644 --- a/test/mock/user.ts +++ b/test/mock/user.ts @@ -30,3 +30,11 @@ export function createMockUser3(organizationId: number | string) { OrganizationId: organizationId, }; } + +export function createMockAdmin() { + return { + email: 'admin@kaist.ac.kr', + password: mockPassword, + role: 'admin', + }; +} diff --git a/test/routes/user.spec.ts b/test/routes/user.spec.ts index 2e5f73d..fd3006f 100644 --- a/test/routes/user.spec.ts +++ b/test/routes/user.spec.ts @@ -142,6 +142,22 @@ describe('API /users', function () { expect(res.body.organization_id).to.equal(organization.id); }); + it('새로운 관리자 계정을 추가할 수 있다.', async function () { + let res = await chai.request(app).post('/users/admin').send({ + email: Mock.mockEmail1, + password: Mock.mockPassword, + }); + expect(res.status).to.equal(200); + expect(res.body.email).to.equal(Mock.mockEmail1); + expect(res.body.role).to.equal('admin'); + + res = await chai.request(app).post('/users/login').send({ + email: Mock.mockEmail1, + password: Mock.mockPassword, + }); + expect(res.status).to.equal(200); + }); + it('이미 등록된 피감기구의 계정을 추가할 수 없다.', async function () { const organization = await model.Organization.create( Mock.mockOrganization1,