-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[Feat/#256] 백오피스 관련 pr입니다.
- Loading branch information
Showing
20 changed files
with
650 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
linkmind/src/main/java/com/app/toaster/admin/common/RedirectResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.app.toaster.admin.common; | ||
|
||
import com.app.toaster.common.dto.ApiResponse; | ||
import com.app.toaster.exception.Success; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class RedirectResponse<T> extends ApiResponse<T> { | ||
private final int code; | ||
private final String redirectUrl; | ||
private T data; | ||
|
||
|
||
public static <T> RedirectResponse<T> success(Success success, String redirectUrl, T data){ | ||
return new RedirectResponse<>(success.getHttpStatusCode(), redirectUrl, data); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
linkmind/src/main/java/com/app/toaster/admin/config/AdminPasswordEncoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.app.toaster.admin.config; | ||
|
||
import com.warrenstrange.googleauth.GoogleAuthenticator; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.crypto.factory.PasswordEncoderFactories; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
||
@Configuration | ||
public class AdminPasswordEncoder { | ||
|
||
@Bean | ||
public PasswordEncoder passwordEncoder(){ | ||
return PasswordEncoderFactories.createDelegatingPasswordEncoder(); | ||
} | ||
|
||
@Bean | ||
public GoogleAuthenticator googleAuthenticator(){ | ||
return new GoogleAuthenticator(); | ||
} | ||
|
||
|
||
} |
89 changes: 89 additions & 0 deletions
89
linkmind/src/main/java/com/app/toaster/admin/config/QrMfaAuthenticator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package com.app.toaster.admin.config; | ||
|
||
import com.app.toaster.admin.entity.VerifiedAdmin; | ||
import com.app.toaster.admin.entity.ToasterAdmin; | ||
import com.app.toaster.admin.infrastructure.VerifiedAdminRepository; | ||
import com.app.toaster.exception.Error; | ||
import com.app.toaster.exception.model.CustomException; | ||
import com.google.zxing.BarcodeFormat; | ||
import com.google.zxing.WriterException; | ||
import com.google.zxing.client.j2se.MatrixToImageWriter; | ||
import com.google.zxing.common.BitMatrix; | ||
import com.google.zxing.qrcode.QRCodeWriter; | ||
import com.warrenstrange.googleauth.GoogleAuthenticator; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import org.springframework.mock.web.MockMultipartFile; | ||
|
||
import javax.imageio.ImageIO; | ||
import java.awt.image.BufferedImage; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
|
||
@Component | ||
@Slf4j | ||
public class QrMfaAuthenticator { | ||
|
||
private String secret; | ||
private final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(); | ||
private final VerifiedAdminRepository verifiedAdminRepository; | ||
|
||
|
||
public QrMfaAuthenticator(@Value("${admin.secret}") final String secret, final VerifiedAdminRepository verifiedAdminRepository) { | ||
this.secret = secret; | ||
this.verifiedAdminRepository = verifiedAdminRepository; | ||
} | ||
|
||
|
||
public MultipartFile generateQrCode(String userKey) { | ||
String data = makeQrDataString(userKey); | ||
QRCodeWriter qrCodeWriter = new QRCodeWriter(); | ||
try { | ||
BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, 240, 240); | ||
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix); | ||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | ||
|
||
ImageIO.write(image, "png", byteArrayOutputStream); | ||
byteArrayOutputStream.close(); | ||
|
||
byte[] qrCodeBytes = byteArrayOutputStream.toByteArray(); | ||
|
||
return new MockMultipartFile("qrCode", "qrcode.png", "image/png", qrCodeBytes); | ||
} catch (WriterException | IOException e) { | ||
log.error(e.getMessage()); | ||
} | ||
return null; | ||
} | ||
|
||
private String makeQrDataString(String userKey) { | ||
return "otpauth://totp/toaster?secret=" + userKey + "&issuer=Google"; | ||
} | ||
|
||
public ToasterAdmin verifyGoogleTotpCode(Integer verificationCode, Long id) { | ||
|
||
VerifiedAdmin admin = verifiedAdminRepository.findById(id).orElseThrow( | ||
() -> new CustomException(Error.NOT_FOUND_USER_EXCEPTION, "어드민이 존재하지않는다.") | ||
); | ||
System.out.println(admin.getAdmin().getUsername()); | ||
|
||
if (verificationCode != null) { | ||
try { | ||
if (!googleAuthenticator.authorize(admin.getOtpSecretKey(), verificationCode)) { | ||
throw new CustomException(Error.BAD_REQUEST_VALIDATION, "유효하지 않은 인증코드입니다."); | ||
} | ||
admin.authorize(); | ||
admin.verifiedAdmin(); | ||
return admin.getAdmin(); | ||
} catch (Exception e) { | ||
log.error("인증 쪽에서 에러 발생."); | ||
} | ||
} else { | ||
throw new CustomException(Error.TOKEN_TIME_EXPIRED_EXCEPTION, "만료된 코드 입니다."); | ||
} | ||
return null; | ||
} | ||
|
||
|
||
} |
40 changes: 40 additions & 0 deletions
40
linkmind/src/main/java/com/app/toaster/admin/config/SwaggerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.app.toaster.admin.config; | ||
|
||
import io.swagger.v3.oas.annotations.OpenAPIDefinition; | ||
import io.swagger.v3.oas.annotations.info.Info; | ||
import io.swagger.v3.oas.models.Components; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.security.SecurityRequirement; | ||
import io.swagger.v3.oas.models.security.SecurityScheme; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
|
||
@OpenAPIDefinition( | ||
info = @Info( | ||
title = "토스터 API 명세서입니다.", | ||
description = "어드민 자격이 필요한 것들은 스웨거 문서를 올리지 않겠습니다.", | ||
version = "v1" | ||
) | ||
) | ||
@Configuration | ||
public class SwaggerConfig { | ||
|
||
private static final String BEARER_TOKEN_PREFIX = "Bearer"; | ||
|
||
@Bean | ||
public OpenAPI openAPI() { | ||
String securityJwtName = "JWT"; | ||
SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityJwtName); | ||
Components components = new Components() | ||
.addSecuritySchemes(securityJwtName, new SecurityScheme() | ||
.name(securityJwtName) | ||
.type(SecurityScheme.Type.HTTP) | ||
.scheme(BEARER_TOKEN_PREFIX) | ||
.bearerFormat(securityJwtName)); | ||
|
||
return new OpenAPI() | ||
.addSecurityItem(securityRequirement) | ||
.components(components); | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
linkmind/src/main/java/com/app/toaster/admin/controller/AdminController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package com.app.toaster.admin.controller; | ||
|
||
import com.app.toaster.admin.common.RedirectResponse; | ||
import com.app.toaster.admin.controller.dto.command.VerifyNewAdminCommand; | ||
import com.app.toaster.admin.controller.dto.request.AdminTotpDto; | ||
import com.app.toaster.admin.controller.dto.request.SignInDto; | ||
import com.app.toaster.admin.controller.dto.response.AdminResponse; | ||
import com.app.toaster.admin.entity.ToasterAdmin; | ||
import com.app.toaster.admin.service.AdminService; | ||
import com.app.toaster.admin.config.QrMfaAuthenticator; | ||
import com.app.toaster.exception.Error; | ||
import com.app.toaster.exception.Success; | ||
import com.app.toaster.exception.model.CustomException; | ||
import com.app.toaster.external.client.aws.S3Service; | ||
import com.app.toaster.external.client.discord.DiscordMessageProvider; | ||
|
||
import com.app.toaster.external.client.discord.NotificationDto; | ||
import com.app.toaster.external.client.discord.NotificationType; | ||
|
||
import jakarta.servlet.http.HttpServletResponse; | ||
import jakarta.servlet.http.HttpSession; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.Model; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
|
||
@Controller | ||
@RequestMapping("/admin") | ||
@RequiredArgsConstructor | ||
class AdminController { | ||
|
||
private final DiscordMessageProvider discordMessageProvider; | ||
private final S3Service s3Service; | ||
private final QrMfaAuthenticator qrMfaAuthenticator; | ||
private final AdminService adminService; | ||
|
||
@PostMapping("/register") | ||
@ResponseBody | ||
public RedirectResponse<?> registerAdmin(@RequestBody SignInDto signInDto, HttpSession session) { | ||
|
||
VerifyNewAdminCommand res = adminService.registerAdmin(signInDto.username(), signInDto.password()); | ||
|
||
String key = res.key(); | ||
Long adminId = res.id(); | ||
boolean isNewAdmin = res.isNewAdmin(); | ||
|
||
if (isNewAdmin){ | ||
key = executeDiscordQrOperation(key); | ||
} | ||
|
||
session.setAttribute("VerifyId", adminId); | ||
session.setAttribute("QrUrl", key); | ||
|
||
return RedirectResponse.success(Success.LOGIN_SUCCESS, "verify",null); | ||
} | ||
|
||
@GetMapping("/register") | ||
public String getRegisterAdmin(Model model, HttpServletResponse response) throws IOException { | ||
return "basic/register"; | ||
} | ||
|
||
@GetMapping("/verify") | ||
public String responseIsAdminCodeView(Model model) { | ||
return "basic/qrForm"; | ||
} | ||
|
||
@GetMapping("/main") | ||
public String adminMain(Model model) { | ||
// model.addAttribute("imageUrl", imageUrl); // imageUrl을 모델에 추가 | ||
return "basic/admin"; | ||
} | ||
|
||
@PostMapping("/verify-code") | ||
@ResponseBody | ||
public RedirectResponse<AdminResponse> responseIsAdminView(HttpSession session, @RequestBody AdminTotpDto request) throws IOException { | ||
//admin인지 판단 | ||
Long verifyId = (Long) session.getAttribute("VerifyId"); | ||
|
||
if (verifyId == null) { | ||
throw new CustomException(Error.BAD_REQUEST_ID, "세션에 VerifyId가 없습니다."); | ||
} | ||
|
||
ToasterAdmin toasterAdmin = qrMfaAuthenticator.verifyGoogleTotpCode(Integer.valueOf(request.code()), verifyId); | ||
|
||
if (toasterAdmin == null){ | ||
throw new CustomException(Error.BAD_REQUEST_ID, "잘못된 유저 입니다."); | ||
} | ||
AdminResponse result = new AdminResponse(toasterAdmin.getUsername()); | ||
s3Service.deleteImage((String) session.getAttribute("QrUrl")); | ||
return RedirectResponse.success(Success.LOGIN_SUCCESS,"main", result); | ||
} | ||
|
||
private String executeDiscordQrOperation(String key){ | ||
MultipartFile qrImage = qrMfaAuthenticator.generateQrCode(key); | ||
String imageKey = s3Service.uploadImage(qrImage, "admin/"); | ||
String qrUrl = s3Service.getURL(imageKey); | ||
discordMessageProvider.sendAdmin(new NotificationDto(NotificationType.ADMIN,null, qrUrl)); | ||
return qrUrl; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...ind/src/main/java/com/app/toaster/admin/controller/dto/command/VerifyNewAdminCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.app.toaster.admin.controller.dto.command; | ||
|
||
public record VerifyNewAdminCommand( | ||
Long id, | ||
String key, | ||
boolean isNewAdmin | ||
) { | ||
} |
6 changes: 6 additions & 0 deletions
6
linkmind/src/main/java/com/app/toaster/admin/controller/dto/request/AdminTotpDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.app.toaster.admin.controller.dto.request; | ||
|
||
public record AdminTotpDto( | ||
String code | ||
) { | ||
} |
7 changes: 7 additions & 0 deletions
7
linkmind/src/main/java/com/app/toaster/admin/controller/dto/request/SignInDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.app.toaster.admin.controller.dto.request; | ||
|
||
public record SignInDto( | ||
String username, | ||
String password | ||
) { | ||
} |
5 changes: 5 additions & 0 deletions
5
linkmind/src/main/java/com/app/toaster/admin/controller/dto/response/AdminResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.app.toaster.admin.controller.dto.response; | ||
|
||
public record AdminResponse(String userName) | ||
{ | ||
} |
60 changes: 60 additions & 0 deletions
60
linkmind/src/main/java/com/app/toaster/admin/entity/ToasterAdmin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package com.app.toaster.admin.entity; | ||
|
||
import jakarta.persistence.*; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.time.LocalDate; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class ToasterAdmin { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "admin_id") | ||
private Long id; | ||
|
||
@Column(name = "username") | ||
private String username; | ||
|
||
@Column(name = "password") | ||
private String password; | ||
|
||
@Column(name = "verified") | ||
private boolean verified; | ||
|
||
@Column(name = "masterToken") | ||
private String masterToken; | ||
|
||
@Column(name = "lastVerifiedDate") | ||
private LocalDate lastTestDate; | ||
|
||
@Builder | ||
public ToasterAdmin(String username, String password){ | ||
this.username = username; | ||
this.password = password; | ||
this.verified = false; | ||
this.lastTestDate = LocalDate.now(); | ||
} | ||
|
||
public VerifiedAdmin authorize(){ | ||
return VerifiedAdmin.builder() | ||
.admin(this) | ||
.build(); | ||
|
||
} | ||
|
||
public void verify(){ | ||
this.lastTestDate = LocalDate.now(); | ||
this.verified = true; | ||
} | ||
|
||
public boolean verifyLastDate(){ | ||
return LocalDate.now().isBefore(this.lastTestDate.plusDays(1)); | ||
} | ||
|
||
} |
Oops, something went wrong.