diff --git a/pom.xml b/pom.xml index d6ef542..6dcea8e 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,11 @@ runtime true + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + 2.4.12 + org.postgresql postgresql @@ -77,7 +82,38 @@ org.springframework.boot spring-boot-starter-webflux + + org.springframework.boot + spring-boot-starter-mail + 3.3.4 + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/config/AppConfig.java b/src/main/java/_11/asktpk/artisanconnectbackend/config/AppConfig.java new file mode 100644 index 0000000..9636a39 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/config/AppConfig.java @@ -0,0 +1,15 @@ +package _11.asktpk.artisanconnectbackend.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class AppConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/config/SecurityConfig.java b/src/main/java/_11/asktpk/artisanconnectbackend/config/SecurityConfig.java new file mode 100644 index 0000000..bf88946 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/config/SecurityConfig.java @@ -0,0 +1,38 @@ +package _11.asktpk.artisanconnectbackend.config; + +import _11.asktpk.artisanconnectbackend.security.JwtRequestFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + private final JwtRequestFilter jwtRequestFilter; + + public SecurityConfig(JwtRequestFilter jwtRequestFilter) { + this.jwtRequestFilter = jwtRequestFilter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .cors(cors -> cors.configure(http)) + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/**").permitAll() + .anyRequest().authenticated()) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/AuthController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/AuthController.java new file mode 100644 index 0000000..231f1df --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/AuthController.java @@ -0,0 +1,126 @@ +package _11.asktpk.artisanconnectbackend.controller; + +import _11.asktpk.artisanconnectbackend.dto.*; +import _11.asktpk.artisanconnectbackend.entities.Client; +import _11.asktpk.artisanconnectbackend.security.JwtUtil; +import _11.asktpk.artisanconnectbackend.service.ClientService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + + private final ClientService clientService; + private final JwtUtil jwtUtil; + + public AuthController(ClientService clientService, JwtUtil jwtUtil) { + this.clientService = clientService; + this.jwtUtil = jwtUtil; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody AuthRequestDTO authRequestDTO) { + if (clientService.checkClientCredentials(authRequestDTO)) { + Client client = clientService.getClientByEmail(authRequestDTO.getEmail()); + Long userId = client.getId(); + String userRole = client.getRole().getRole(); + + String token = jwtUtil.generateToken(client.getEmail(), userRole, userId); + + log.info("User logged in with {}", client.getEmail()); + return ResponseEntity.status(HttpStatus.OK) + .body(new AuthResponseDTO(userId, userRole, token)); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(null); + } + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody ClientRegistrationDTO clientDTO) { + if (clientService.getClientByEmail(clientDTO.getEmail()) != null) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + + ClientDTO savedClient = clientService.registerClient(clientDTO); + + String token = jwtUtil.generateToken( + savedClient.getEmail(), + savedClient.getRole(), + savedClient.getId() + ); + + log.info("New user registered with {}", savedClient.getEmail()); + + return ResponseEntity.status(HttpStatus.CREATED) + .body(new AuthResponseDTO( + savedClient.getId(), + savedClient.getRole(), + token + )); + } + + @PostMapping("/logout") + public ResponseEntity logout(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + String token = authHeader.substring(7); + jwtUtil.blacklistToken(token); + return ResponseEntity.ok(new RequestResponseDTO("Successfully logged out")); + } + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new RequestResponseDTO("Invalid token")); + } + + @PostMapping("/google") + public ResponseEntity authenticateWithGoogle(@RequestBody GoogleAuthRequestDTO dto) { + try { + String accessToken = dto.getGoogleToken(); + String googleUserInfoUrl = "https://www.googleapis.com/oauth2/v3/userinfo"; + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + HttpEntity entity = new HttpEntity<>(headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange( + googleUserInfoUrl, HttpMethod.GET, entity, Map.class); + + Map userInfo = response.getBody(); + +// String googleId = (String) userInfo.get("sub"); Potencjalnie możemy używać googlowskiego ID, ale to ma konflikt z naszym generowanym + assert userInfo != null; + String email = (String) userInfo.get("email"); + String name = (String) userInfo.get("name"); + + Client client = clientService.getClientByEmail(email); + if (client == null) { + client = new Client(); + client.setEmail(email); + client.setFirstName(name); + client.setRole(clientService.getUserRole()); // to pobiera po prostu role "USER" z tabeli w bazie + clientService.saveClientToDB(client); + } + + String jwt = jwtUtil.generateToken(client.getEmail(), client.getRole().getRole(), client.getId()); + + log.info("User authenticated with google: {}", email); + return ResponseEntity.ok(new AuthResponseDTO(client.getId(), client.getRole().getRole(), jwt)); + } catch (HttpClientErrorException httpClientErrorException) { + log.error("Token is invalid or expired"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new RequestResponseDTO("Invalid access token")); + } catch (Exception e) { + log.error("Error while checking Google access token", e); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(new RequestResponseDTO("Authentication Error (Google): " + e.getMessage())); + } + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/ClientController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/ClientController.java index b20fddd..f5ba9a5 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/controller/ClientController.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/ClientController.java @@ -24,16 +24,16 @@ public class ClientController { } @GetMapping("/get/{id}") - public ResponseEntity getClientById(@PathVariable long id) { + public ResponseEntity getClientById(@PathVariable long id) { if(clientService.getClientById(id) != null) { - return new ResponseEntity(clientService.getClientById(id), HttpStatus.OK); + return new ResponseEntity<>(clientService.getClientByIdDTO(id), HttpStatus.OK); } else { - return new ResponseEntity(HttpStatus.NOT_FOUND); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } @PostMapping("/add") - public ResponseEntity addClient(@RequestBody ClientDTO clientDTO) { + public ResponseEntity addClient(@RequestBody ClientDTO clientDTO) { if(clientService.clientExists(clientDTO.getId())) { return new ResponseEntity<>(HttpStatus.CONFLICT); } else { @@ -43,7 +43,7 @@ public class ClientController { // TODO: do zrobienia walidacja danych @PutMapping("/edit/{id}") - public ResponseEntity updateClient(@PathVariable("id") long id, @RequestBody ClientDTO clientDTO) { + public ResponseEntity updateClient(@PathVariable("id") long id, @RequestBody ClientDTO clientDTO) { if(clientService.clientExists(id)) { return new ResponseEntity<>(clientService.updateClient(id, clientDTO),HttpStatus.OK); } else { @@ -52,7 +52,7 @@ public class ClientController { } @DeleteMapping("/delete/{id}") - public ResponseEntity deleteClient(@PathVariable("id") long id) { + public ResponseEntity deleteClient(@PathVariable("id") long id) { if(clientService.clientExists(id)) { clientService.deleteClient(id); return new ResponseEntity<>(HttpStatus.OK); diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/EmailController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/EmailController.java new file mode 100644 index 0000000..69dddc2 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/EmailController.java @@ -0,0 +1,25 @@ +package _11.asktpk.artisanconnectbackend.controller; +import _11.asktpk.artisanconnectbackend.dto.EmailDTO; +import _11.asktpk.artisanconnectbackend.service.EmailService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/email") +public class EmailController { + private final EmailService emailService; + + public EmailController(EmailService emailService) { + this.emailService = emailService; + } + + @PostMapping("/send") + public ResponseEntity sendEmail(@RequestBody EmailDTO email) { + try { + emailService.sendEmail(email); + return ResponseEntity.ok("Email wysłany pomyślnie"); + } catch (Exception e) { + return ResponseEntity.status(500).body("Błąd podczas wysyłania emaila"); + } + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/OrderController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/OrderController.java index f557e3b..6571d3b 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/controller/OrderController.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/OrderController.java @@ -43,9 +43,11 @@ public class OrderController { paymentDescription += order.getNotice().getTitle(); TransactionPaymentRequestDTO request = new TransactionPaymentRequestDTO( order.getAmount(), paymentDescription, payer); + String response = paymentService.createTransaction(order,authPaymentDTO.getAccess_token(), request); System.out.println(response); + System.out.println(request); - return ResponseEntity.ok(authPaymentDTO.getAccess_token()); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/PaymentController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/PaymentController.java new file mode 100644 index 0000000..8449e5c --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/PaymentController.java @@ -0,0 +1,102 @@ +package _11.asktpk.artisanconnectbackend.controller; + +import _11.asktpk.artisanconnectbackend.entities.Notice; +import _11.asktpk.artisanconnectbackend.entities.Order; +import _11.asktpk.artisanconnectbackend.entities.Payment; +import _11.asktpk.artisanconnectbackend.repository.PaymentRepository; +import _11.asktpk.artisanconnectbackend.utils.Enums; +import org.springframework.beans.factory.annotation.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.DigestUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/v1/payments") +public class PaymentController { + + @Value("${tpay.securityCode}") + private String sellerSecurityCode; + + private static final Logger log = LoggerFactory.getLogger(PaymentController.class); + + private final PaymentRepository paymentRepository; + + public PaymentController(PaymentRepository paymentRepository) { + this.paymentRepository = paymentRepository; + } + + @PostMapping(value = "/notification", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public ResponseEntity handleTpayNotification(@RequestParam Map params) { + log.info("=== ODEBRANO NOTYFIKACJĘ Tpay ==="); + log.info("Parametry:\n{}", paramsToLogString(params)); + + String id = params.get("id"); + String trId = params.get("tr_id"); + String trAmount = params.get("tr_amount"); + String trCrc = params.get("tr_crc"); + String md5sum = params.get("md5sum"); + String trStatus = params.get("tr_status"); + + String expectedMd5 = DigestUtils.md5DigestAsHex( + (id + trId + trAmount + trCrc + sellerSecurityCode).getBytes() + ); + + if (!expectedMd5.equals(md5sum)) { + log.warn("❌ Błędna suma kontrolna! Otrzymano: {}, Oczekiwano: {}", md5sum, expectedMd5); + return ResponseEntity.status(400).body("INVALID CHECKSUM"); + } + + Optional optionalPayment = paymentRepository.findByTransactionId(trId); + if (optionalPayment.isPresent()) { + Payment payment = optionalPayment.get(); + + if ("true".equalsIgnoreCase(trStatus) || "PAID".equalsIgnoreCase(trStatus)) { + log.info("✅ Transakcja opłacona: tr_id={}, kwota={}", trId, params.get("tr_paid")); + payment.setStatus(Enums.PaymentStatus.CORRECT); + + if (payment.getOrder() != null) { + Order order = payment.getOrder(); + order.setStatus(Enums.OrderStatus.COMPLETED); + Notice notice = order.getNotice(); + if (order.getOrderType() == Enums.OrderType.ACTIVATION) { + notice.setStatus(Enums.Status.ACTIVE); + } else if (order.getOrderType() == Enums.OrderType.BOOST) { + notice.setPublishDate(LocalDateTime.now()); + } + } + + } else if ("false".equalsIgnoreCase(trStatus)) { + log.warn("❌ Transakcja nieudana: {}", trId); + payment.setStatus(Enums.PaymentStatus.INCORRECT); + + if (payment.getOrder() != null) { + payment.getOrder().setStatus(Enums.OrderStatus.CANCELLED); + } + } + + paymentRepository.save(payment); + } else { + log.warn("⚠️ Brak płatności o tr_id={}", trId); + } + + return ResponseEntity.ok("TRUE"); + } + + private String paramsToLogString(Map params) { + return params.entrySet().stream() + .map(e -> e.getKey() + " = " + e.getValue()) + .collect(Collectors.joining("\n")); + } +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/VariablesController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/VariablesController.java index 009aeb4..e9767ba 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/controller/VariablesController.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/VariablesController.java @@ -13,7 +13,6 @@ import java.util.Map; @RestController @RequestMapping("/api/v1/vars") public class VariablesController { - @GetMapping("/categories") public List getAllVariables() { List categoriesDTOList = new ArrayList<>(); @@ -31,10 +30,4 @@ public class VariablesController { public List getAllStatuses() { return List.of(Enums.Status.values()); } - - @GetMapping("/roles") - public List getAllRoles() { - return List.of(Enums.Role.values()); - } - } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/controller/WishlistController.java b/src/main/java/_11/asktpk/artisanconnectbackend/controller/WishlistController.java index c30f654..5e95337 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/controller/WishlistController.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/controller/WishlistController.java @@ -24,10 +24,10 @@ public class WishlistController { this.noticeService = noticeService; } - @PostMapping("/toggle") - public ResponseEntity toggleWishlist(@RequestBody WishlistDTO wishlistDTO) { - Long noticeId = wishlistDTO.getNoticeId(); - Long clientId = wishlistDTO.getClientId(); + @PostMapping("/toggle/{noticeId}") + public ResponseEntity toggleWishlist(@PathVariable Long noticeId) { + + Long clientId = 1L; NoticeDTO noticeDTO = noticeService.getNoticeById(noticeId); if (noticeDTO == null) { return ResponseEntity.badRequest().body(new RequestResponseDTO("Notice not found")); diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthRequestDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthRequestDTO.java new file mode 100644 index 0000000..3c37189 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthRequestDTO.java @@ -0,0 +1,10 @@ +package _11.asktpk.artisanconnectbackend.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class AuthRequestDTO { + private String email; + private String password; +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthResponseDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthResponseDTO.java new file mode 100644 index 0000000..5c76d39 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/AuthResponseDTO.java @@ -0,0 +1,12 @@ +package _11.asktpk.artisanconnectbackend.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter @AllArgsConstructor +public class AuthResponseDTO { + private Long user_id; + private String user_role; + private String token; +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientDTO.java index 09fde80..ab6af54 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientDTO.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientDTO.java @@ -6,8 +6,6 @@ import lombok.Setter; import jakarta.validation.constraints.Email; -import _11.asktpk.artisanconnectbackend.utils.Enums.Role; - @Getter @Setter public class ClientDTO { private Long id; @@ -18,5 +16,5 @@ public class ClientDTO { private String firstName; private String lastName; private String image; - private Role role; + private String role; } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientRegistrationDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientRegistrationDTO.java new file mode 100644 index 0000000..4d4cd07 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/ClientRegistrationDTO.java @@ -0,0 +1,16 @@ +package _11.asktpk.artisanconnectbackend.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class ClientRegistrationDTO { + @Email + @NotBlank + private String email; + private String firstName; + private String lastName; + private String password; +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/EmailDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/EmailDTO.java new file mode 100644 index 0000000..ead076f --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/EmailDTO.java @@ -0,0 +1,20 @@ +package _11.asktpk.artisanconnectbackend.dto; + +import lombok.Getter; +import lombok.Setter; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +@Getter +@Setter +public class EmailDTO { + @Email(message = "Podaj poprawny adres email") + @NotBlank(message = "Adres email nie może być pusty") + private String to; + + @NotBlank(message = "Temat nie może być pusty") + private String subject; + + @NotBlank(message = "Treść nie może być pusta") + private String body; +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/GoogleAuthRequestDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/GoogleAuthRequestDTO.java new file mode 100644 index 0000000..df94256 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/GoogleAuthRequestDTO.java @@ -0,0 +1,9 @@ +package _11.asktpk.artisanconnectbackend.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class GoogleAuthRequestDTO { + private String googleToken; +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/RequestResponseDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/RequestResponseDTO.java index 96a0ed5..845258d 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/dto/RequestResponseDTO.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/RequestResponseDTO.java @@ -10,4 +10,8 @@ public class RequestResponseDTO { public RequestResponseDTO(String message) { this.message = message; } + + public String toJSON() { + return "{\"message\":\"" + message + "\"}"; + } } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentRequestDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentRequestDTO.java index 8ef1123..76736d3 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentRequestDTO.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentRequestDTO.java @@ -1,14 +1,12 @@ package _11.asktpk.artisanconnectbackend.dto; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@ToString public class TransactionPaymentRequestDTO { private double amount; private String description; diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentResponseDTO.java b/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentResponseDTO.java index 909219f..2f2c9af 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentResponseDTO.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/dto/TransactionPaymentResponseDTO.java @@ -54,6 +54,27 @@ import lombok.Setter; private double amountPaid; private DateInfo date; } + + @Override + public String toString() { + return "YourClassName{" + + "result='" + result + '\'' + + ", requestId='" + requestId + '\'' + + ", transactionId='" + transactionId + '\'' + + ", title='" + title + '\'' + + ", posId='" + posId + '\'' + + ", status='" + status + '\'' + + ", date=" + date + + ", amount=" + amount + + ", currency='" + currency + '\'' + + ", description='" + description + '\'' + + ", hiddenDescription='" + hiddenDescription + '\'' + + ", payer=" + payer + + ", payments=" + payments + + ", transactionPaymentUrl='" + transactionPaymentUrl + '\'' + + '}'; + } + } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/entities/Client.java b/src/main/java/_11/asktpk/artisanconnectbackend/entities/Client.java index 0a25d3d..c6ca7c0 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/entities/Client.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/entities/Client.java @@ -1,11 +1,11 @@ package _11.asktpk.artisanconnectbackend.entities; -import _11.asktpk.artisanconnectbackend.utils.Enums.Role; - import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import java.util.Date; import java.util.List; @Entity @@ -24,14 +24,15 @@ public class Client { private String lastName; - private String image; // Optional field + private String image; - @Enumerated(EnumType.STRING) + @ManyToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "role_id", referencedColumnName = "id") private Role role; -// @OneToMany(mappedBy = "client", cascade = CascadeType.ALL) -// private List notices; - @OneToMany(mappedBy = "client", cascade = CascadeType.ALL) private List orders; + + @CreationTimestamp + private Date createdAt; } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/entities/GlobalVariables.java b/src/main/java/_11/asktpk/artisanconnectbackend/entities/GlobalVariables.java deleted file mode 100644 index cb50f6f..0000000 --- a/src/main/java/_11/asktpk/artisanconnectbackend/entities/GlobalVariables.java +++ /dev/null @@ -1,16 +0,0 @@ -package _11.asktpk.artisanconnectbackend.entities; - -import jakarta.persistence.*; - -@Entity -@Table(name = "global_variables") -public class GlobalVariables { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - private String value; - - // Getters, setters, and constructors -} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/entities/Role.java b/src/main/java/_11/asktpk/artisanconnectbackend/entities/Role.java new file mode 100644 index 0000000..ec2d8a8 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/entities/Role.java @@ -0,0 +1,19 @@ +package _11.asktpk.artisanconnectbackend.entities; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "roles") +@Getter +@Setter +public class Role { + @Id + private Long id; + @Column(name="rolename") + private String role; +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/repository/ClientRepository.java b/src/main/java/_11/asktpk/artisanconnectbackend/repository/ClientRepository.java index d4d07b7..eccd51f 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/repository/ClientRepository.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/repository/ClientRepository.java @@ -1,8 +1,8 @@ package _11.asktpk.artisanconnectbackend.repository; import _11.asktpk.artisanconnectbackend.entities.Client; - import org.springframework.data.jpa.repository.JpaRepository; public interface ClientRepository extends JpaRepository { + Client findByEmail(String email); } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/repository/PaymentRepository.java b/src/main/java/_11/asktpk/artisanconnectbackend/repository/PaymentRepository.java index 922dc58..486d80d 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/repository/PaymentRepository.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/repository/PaymentRepository.java @@ -4,7 +4,9 @@ import _11.asktpk.artisanconnectbackend.entities.Payment; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface PaymentRepository extends JpaRepository { - + Optional findByTransactionId(String transactionId); } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/repository/RolesRepository.java b/src/main/java/_11/asktpk/artisanconnectbackend/repository/RolesRepository.java new file mode 100644 index 0000000..0ba644e --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/repository/RolesRepository.java @@ -0,0 +1,12 @@ +package _11.asktpk.artisanconnectbackend.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import _11.asktpk.artisanconnectbackend.entities.Role; + +@Repository +public interface RolesRepository extends JpaRepository { + Role findRoleById(Long id); + + Role findRoleByRole(String role); +} diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtRequestFilter.java b/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtRequestFilter.java new file mode 100644 index 0000000..035d373 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtRequestFilter.java @@ -0,0 +1,79 @@ +package _11.asktpk.artisanconnectbackend.security; + +import _11.asktpk.artisanconnectbackend.dto.RequestResponseDTO; +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +@Component +public class JwtRequestFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + + public JwtRequestFilter(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain chain) + throws ServletException, IOException { + + final String authorizationHeader = request.getHeader("Authorization"); + + String email = null; + String jwt = null; + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + jwt = authorizationHeader.substring(7); + + try { + if (jwtUtil.isBlacklisted(jwt) || !jwtUtil.isLatestToken(jwt)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + String jsonResponse = "{\"error\": \"Token is invalid or expired. Please login again.\"}"; + response.getWriter().write(jsonResponse); + return; + } + + + email = jwtUtil.extractEmail(jwt); + } catch (ExpiredJwtException expiredJwtException) { + logger.error(expiredJwtException.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } catch (Exception e) { + logger.error(e.getMessage()); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write(new RequestResponseDTO(e.getMessage()).toJSON()); + return; + } + } + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + String role = jwtUtil.extractRole(jwt); + + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + email, null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role))); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + +// logger.info("Token of user " + jwtUtil.extractEmail(jwt) + (jwtUtil.isTokenExpired(jwt) ? " is expired" : " is not expired")); + + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtUtil.java b/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtUtil.java new file mode 100644 index 0000000..ae36e06 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/security/JwtUtil.java @@ -0,0 +1,97 @@ +package _11.asktpk.artisanconnectbackend.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@Component +public class JwtUtil { + + @Value("${jwt.secret:defaultSecretKeyNeedsToBeAtLeast32BytesLong}") + private String secret; + + @Value("${jwt.expiration}") + private long expiration; + + // sterowanie tokenami wygasnietymi + private final Set blacklistedTokens = ConcurrentHashMap.newKeySet(); + + public void blacklistToken(String token) { + blacklistedTokens.add(token); + } + + public boolean isBlacklisted(String token) { + return blacklistedTokens.contains(token); + } + + + private SecretKey getSigningKey() { + return Keys.hmacShaKeyFor(secret.getBytes()); + } + + private final Map userActiveTokens = new ConcurrentHashMap<>(); + + public boolean isLatestToken(String token) { + String email = extractEmail(token); + String tokenId = extractTokenId(token); + String latestTokenId = userActiveTokens.get(email); + + return latestTokenId != null && latestTokenId.equals(tokenId); + } + + public String generateToken(String email, String role, Long userId) { + Map claims = new HashMap<>(); + claims.put("role", role); + claims.put("userId", userId); + claims.put("tokenId", UUID.randomUUID().toString()); + + String token = createToken(claims, email); + + userActiveTokens.put(email, extractTokenId(token)); + + return token; + } + + private String createToken(Map claims, String subject) { + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public String extractTokenId(String token) { + return extractAllClaims(token).get("tokenId", String.class); + } + + public String extractEmail(String token) { + return extractClaim(token, Claims::getSubject); + } + + public String extractRole(String token) { + return extractAllClaims(token).get("role", String.class); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/service/ClientService.java b/src/main/java/_11/asktpk/artisanconnectbackend/service/ClientService.java index 2394015..36af97b 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/service/ClientService.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/service/ClientService.java @@ -1,9 +1,14 @@ package _11.asktpk.artisanconnectbackend.service; +import _11.asktpk.artisanconnectbackend.dto.AuthRequestDTO; import _11.asktpk.artisanconnectbackend.dto.ClientDTO; +import _11.asktpk.artisanconnectbackend.dto.ClientRegistrationDTO; import _11.asktpk.artisanconnectbackend.entities.Client; +import _11.asktpk.artisanconnectbackend.entities.Role; import _11.asktpk.artisanconnectbackend.repository.ClientRepository; +import _11.asktpk.artisanconnectbackend.repository.RolesRepository; import jakarta.persistence.EntityNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; @@ -11,19 +16,27 @@ import java.util.List; @Service public class ClientService { private final ClientRepository clientRepository; + private final PasswordEncoder passwordEncoder; + private final RolesRepository rolesRepository; - public ClientService(ClientRepository clientRepository) { + public ClientService(ClientRepository clientRepository, PasswordEncoder passwordEncoder, RolesRepository rolesRepository) { this.clientRepository = clientRepository; + this.passwordEncoder = passwordEncoder; + this.rolesRepository = rolesRepository; } private ClientDTO toDto(Client client) { + if(client == null) { + return null; + } + ClientDTO dto = new ClientDTO(); dto.setId(client.getId()); dto.setFirstName(client.getFirstName()); dto.setLastName(client.getLastName()); dto.setEmail(client.getEmail()); - dto.setRole(client.getRole()); + dto.setRole(client.getRole().getRole()); dto.setImage(client.getImage()); return dto; @@ -31,17 +44,35 @@ public class ClientService { private Client fromDto(ClientDTO dto) { Client client = new Client(); + Role rola; + + if (clientRepository.findById(dto.getId()).isPresent()) { + rola = clientRepository.findById(dto.getId()).get().getRole(); + } else { + rola = new Role(); + rola.setRole("USER"); + } client.setId(dto.getId()); client.setFirstName(dto.getFirstName()); client.setLastName(dto.getLastName()); client.setEmail(dto.getEmail()); - client.setRole(dto.getRole()); + client.setRole(rola); client.setImage(dto.getImage()); return client; } + private Client fromDto(ClientRegistrationDTO dto) { + Client client = new Client(); + + client.setFirstName(dto.getFirstName()); + client.setLastName(dto.getLastName()); + client.setEmail(dto.getEmail()); + client.setPassword(dto.getPassword()); + return client; + } + public List getAllClients() { List clients = clientRepository.findAll(); return clients.stream().map(this::toDto).toList(); @@ -51,6 +82,10 @@ public class ClientService { return clientRepository.findById(id).orElse(null); } + public ClientDTO getClientByIdDTO(Long id) { + return toDto(clientRepository.findById(id).orElse(null)); + } + public boolean clientExists(Long id) { return clientRepository.existsById(id); } @@ -59,15 +94,21 @@ public class ClientService { return toDto(clientRepository.save(fromDto(clientDTO))); } + public Client saveClientToDB(Client client) { + return clientRepository.save(client); + } + public ClientDTO updateClient(long id, ClientDTO clientDTO) { Client existingClient = clientRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id)); + Role newRole = rolesRepository.findRoleByRole(clientDTO.getRole()); + existingClient.setEmail(clientDTO.getEmail()); existingClient.setFirstName(clientDTO.getFirstName()); existingClient.setLastName(clientDTO.getLastName()); existingClient.setImage(clientDTO.getImage()); - existingClient.setRole(clientDTO.getRole()); + existingClient.setRole(newRole); return toDto(clientRepository.save(existingClient)); } @@ -75,4 +116,30 @@ public class ClientService { public void deleteClient(Long id) { clientRepository.deleteById(id); } + + // И замените метод checkClientCredentials на: + public boolean checkClientCredentials(AuthRequestDTO dto) { + Client cl = clientRepository.findByEmail(dto.getEmail()); + if (cl == null) { + return false; + } + + return passwordEncoder.matches(dto.getPassword(), cl.getPassword()); + } + + // При создании нового пользователя не забудьте шифровать пароль: + public ClientDTO registerClient(ClientRegistrationDTO clientDTO) { + Client client = fromDto(clientDTO); + client.setRole(rolesRepository.findRoleById(1L)); + client.setPassword(passwordEncoder.encode(client.getPassword())); + return toDto(clientRepository.save(client)); + } + + public Client getClientByEmail(String email) { + return clientRepository.findByEmail(email); + } + + public Role getUserRole() { + return rolesRepository.findRoleByRole("USER"); + } } diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/service/EmailService.java b/src/main/java/_11/asktpk/artisanconnectbackend/service/EmailService.java new file mode 100644 index 0000000..fbb6474 --- /dev/null +++ b/src/main/java/_11/asktpk/artisanconnectbackend/service/EmailService.java @@ -0,0 +1,24 @@ +package _11.asktpk.artisanconnectbackend.service; + +import _11.asktpk.artisanconnectbackend.dto.EmailDTO; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +public class EmailService { + private final JavaMailSender mailSender; + + public EmailService(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + public void sendEmail(EmailDTO email) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(email.getTo()); + message.setSubject(email.getSubject()); + message.setText(email.getBody()); + message.setFrom("patryk.kania001@gmail.com"); + mailSender.send(message); + } +} \ No newline at end of file diff --git a/src/main/java/_11/asktpk/artisanconnectbackend/service/PaymentService.java b/src/main/java/_11/asktpk/artisanconnectbackend/service/PaymentService.java index f0640e2..aee6bd5 100644 --- a/src/main/java/_11/asktpk/artisanconnectbackend/service/PaymentService.java +++ b/src/main/java/_11/asktpk/artisanconnectbackend/service/PaymentService.java @@ -69,10 +69,12 @@ public class PaymentService { payment.setStatus(Enums.PaymentStatus.PENDING); - payment.setTransactionId(response.getTransactionId()); + payment.setTransactionId(response.getTitle()); payment.setTransactionPaymentUrl(response.getTransactionPaymentUrl()); paymentRepository.save(payment); + System.out.println(response); + return response.getTransactionPaymentUrl(); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ca1826a..8fc698f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,8 +18,22 @@ file.upload-dir=/Users/andsol/Desktop/uploads spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=10MB +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=patryk.kania001@gmail.com +spring.mail.password=pmyd ylwg mbsn hcpp +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true + tpay.clientId = 01JQKC048X62ST9V59HNRSXD92-01JQKC2CQHPYXQFSFX8BKC24BX tpay.clientSecret = 44898642be53381cdcc47f3e44bf5a15e592f5d270fc3a6cf6fb81a8b8ebffb9 tpay.authUrl = https://openapi.sandbox.tpay.com/oauth/auth tpay.transactionUrl = https://openapi.sandbox.tpay.com/transactions +tpay.securityCode = )IY7E)YSM!A)Q6O-GN#U7U_33s9qObk8 +#jwt settings +jwt.secret=DIXLsOs3FKmCAQwISd0SKsHMXJrPl3IKIRkVlkOvYW7kEcdUTbxh8zFe1B3eZWkY +jwt.expiration=300000 + +logging.file.name=logs/payment-notifications.log +logging.level.TpayLogger=INFO \ No newline at end of file diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index 3a9c65e..d9c2989 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -1,10 +1,15 @@ -INSERT INTO clients (email, first_name, image, last_name, password, role) +INSERT INTO roles (id, rolename) VALUES - ('dignissim.tempor.arcu@aol.ca', 'Diana', 'null', 'Harrison', 'password', 'USER'), - ('john.doe@example.com', 'John', 'null', 'Doe', 'password123', 'ADMIN'), - ('jane.smith@example.com', 'Jane', 'null', 'Smith', 'securepass', 'USER'), - ('michael.brown@example.com', 'Michael', 'null', 'Brown', 'mypassword', 'USER'), - ('emily.jones@example.com', 'Emily', 'null', 'Jones', 'passw0rd', 'USER'); + (1, 'USER'), + (2, 'ADMIN'); + +INSERT INTO clients (email, first_name, last_name, password, role_id) +VALUES + ('dignissim.tempor.arcu@aol.ca', 'Diana', 'Harrison', 'password', 1), + ('john.doe@example.com', 'John', 'Doe', 'password123', 2), + ('jane.smith@example.com', 'Jane', 'Smith', 'securepass', 1), + ('michael.brown@example.com', 'Michael', 'Brown', 'mypassword', 1), + ('emily.jones@example.com', 'Emily', 'Jones', 'passw0rd', 1); INSERT INTO notice (title, description, client_id, price, category, status, publish_date) VALUES