diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..f6a8b53
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+version: '3.8'
+services:
+ api:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - "8080:8080" # todo: ?change
+ restart: unless-stopped
+ environment:
+ - SERVER_PORT=8080
+ - SPRING_DATASOURCE_URL=jdbc:postgresql://hotel_db
+ - SPRING_DATASOURCE_USERNAME=admin
+ - SPRING_DATASOURCE_PASSWORD=secure_pwd
+
+ postgres:
+ image: postgres:14.7-alpine
+ environment:
+ POSTGRES_DB: hotel_db
+ POSTGRES_USER: admin
+ POSTGRES_PASSWORD: secure_pwd
+ ports:
+ - "15432:5432"
+ #volumes:
+ # - db-data
+ restart: unless-stopped
+
+#volumes:
+# db-data:
diff --git a/pom.xml b/pom.xml
index 28d9035..3a5d2bf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,10 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-security
+
com.h2database
h2
@@ -88,6 +92,26 @@
springdoc-openapi-starter-webmvc-ui
2.8.8
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+
+
+ com.google.guava
+ guava
+ 32.1.3-jre
+
\ No newline at end of file
diff --git a/src/main/java/com/example/nto/configuration/JwtAuthFilter.java b/src/main/java/com/example/nto/configuration/JwtAuthFilter.java
new file mode 100644
index 0000000..f2232da
--- /dev/null
+++ b/src/main/java/com/example/nto/configuration/JwtAuthFilter.java
@@ -0,0 +1,53 @@
+package com.example.nto.configuration;
+
+import com.example.nto.service.impl.EmployeeDetailsService;
+import com.example.nto.service.impl.TokenService;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class JwtAuthFilter extends OncePerRequestFilter {
+
+ private final TokenService tokenService;
+ private final EmployeeDetailsService userDetailsService;
+
+ public JwtAuthFilter(TokenService tokenService, EmployeeDetailsService userDetailsService) {
+ this.tokenService = tokenService;
+ this.userDetailsService = userDetailsService;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ String authHeader = request.getHeader("Authorization");
+ String token = null;
+ String username = null;
+
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ token = authHeader.substring(7);
+ username = tokenService.extractUsername(token);
+ }
+
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+ if (tokenService.validateToken(token, userDetails)) {
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+ userDetails,
+ null,
+ userDetails.getAuthorities());
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/src/main/java/com/example/nto/configuration/SecurityConfig.java b/src/main/java/com/example/nto/configuration/SecurityConfig.java
new file mode 100644
index 0000000..ea336c8
--- /dev/null
+++ b/src/main/java/com/example/nto/configuration/SecurityConfig.java
@@ -0,0 +1,59 @@
+package com.example.nto.configuration;
+
+import com.example.nto.service.impl.EmployeeDetailsService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+ private final JwtAuthFilter jwtAuthFilter;
+ private final EmployeeDetailsService userDetailsService;
+
+ public SecurityConfig(JwtAuthFilter jwtAuthFilter, EmployeeDetailsService userDetailsService) {
+ this.jwtAuthFilter = jwtAuthFilter;
+ this.userDetailsService = userDetailsService;
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpRequest) throws Exception {
+ httpRequest.csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/auth/register", "api/auth/login").permitAll()
+ .anyRequest().authenticated())
+ .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return httpRequest.build();
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+ provider.setUserDetailsService(userDetailsService);
+ provider.setPasswordEncoder(passwordEncoder());
+ return provider;
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/com/example/nto/controller/AuthorizationController.java b/src/main/java/com/example/nto/controller/AuthorizationController.java
new file mode 100644
index 0000000..98aaf13
--- /dev/null
+++ b/src/main/java/com/example/nto/controller/AuthorizationController.java
@@ -0,0 +1,31 @@
+package com.example.nto.controller;
+
+import com.example.nto.controller.dto.CreateAccountDto;
+import com.example.nto.controller.dto.LoginDto;
+import com.example.nto.service.AuthorizationService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("api/auth")
+public class AuthorizationController {
+
+ private final AuthorizationService authorizationService;
+
+ public AuthorizationController(AuthorizationService authorizationService) {
+ this.authorizationService = authorizationService;
+ }
+
+ @PostMapping("register")
+ public ResponseEntity> register(@RequestBody CreateAccountDto dto) {
+ return ResponseEntity.status(201).body(authorizationService.createUser(dto));
+ }
+
+ @PostMapping("login")
+ public ResponseEntity> login(@RequestBody LoginDto dto) {
+ return ResponseEntity.status(201).body(authorizationService.loginUser(dto));
+ }
+}
diff --git a/src/main/java/com/example/nto/controller/BookingController.java b/src/main/java/com/example/nto/controller/BookingController.java
index 7ae64a7..b406ec7 100644
--- a/src/main/java/com/example/nto/controller/BookingController.java
+++ b/src/main/java/com/example/nto/controller/BookingController.java
@@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
-
+/*
@Validated
@RestController
@RequestMapping("api")
@@ -31,3 +31,4 @@ public class BookingController {
bookingService.create(code, bookingCreateDto);
}
}
+*/
\ No newline at end of file
diff --git a/src/main/java/com/example/nto/controller/EmployeeController.java b/src/main/java/com/example/nto/controller/EmployeeController.java
index e913fe8..4d4c340 100644
--- a/src/main/java/com/example/nto/controller/EmployeeController.java
+++ b/src/main/java/com/example/nto/controller/EmployeeController.java
@@ -11,8 +11,9 @@ import org.springframework.web.bind.annotation.*;
@RequiredArgsConstructor
public class EmployeeController {
- private final EmployeeService employeeService;
+ // private final EmployeeService employeeService;
+ /*
@GetMapping("/{code}/auth")
@ResponseStatus(code = HttpStatus.OK)
public void login(@PathVariable String code) {
@@ -24,4 +25,6 @@ public class EmployeeController {
public EmployeeDto getByCode(@PathVariable String code) {
return employeeService.getByCode(code);
}
+ */
+
}
diff --git a/src/main/java/com/example/nto/controller/dto/CreateAccountDto.java b/src/main/java/com/example/nto/controller/dto/CreateAccountDto.java
new file mode 100644
index 0000000..80dedba
--- /dev/null
+++ b/src/main/java/com/example/nto/controller/dto/CreateAccountDto.java
@@ -0,0 +1,14 @@
+package com.example.nto.controller.dto;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@Builder
+// @RequiredArgsConstructor
+public class CreateAccountDto {
+ private String username;
+ private String password;
+ private String deviceName;
+}
diff --git a/src/main/java/com/example/nto/controller/dto/LoginDto.java b/src/main/java/com/example/nto/controller/dto/LoginDto.java
new file mode 100644
index 0000000..d304331
--- /dev/null
+++ b/src/main/java/com/example/nto/controller/dto/LoginDto.java
@@ -0,0 +1,9 @@
+package com.example.nto.controller.dto;
+
+import lombok.Data;
+
+@Data
+public class LoginDto {
+ private String username;
+ private String password;
+}
diff --git a/src/main/java/com/example/nto/entity/Employee.java b/src/main/java/com/example/nto/entity/Employee.java
index f0c4411..a2050d5 100644
--- a/src/main/java/com/example/nto/entity/Employee.java
+++ b/src/main/java/com/example/nto/entity/Employee.java
@@ -1,11 +1,19 @@
package com.example.nto.entity;
import jakarta.persistence.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Set;
+
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
@Data
@Entity
@@ -13,7 +21,7 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "employee")
-public class Employee {
+public class Employee implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -22,12 +30,33 @@ public class Employee {
@Column(name = "name")
private String name;
- @Column(name = "code")
- private String code;
+ @Column(name = "password")
+ private String password;
@Column(name = "photo_url")
private String photoUrl;
+ // @Enumerated
+ @Column(name = "roles")
+ private Set roles;
+
+ @OneToMany(mappedBy="employee")
+ private Set sessions;
+
@OneToMany(mappedBy = "employee", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List bookingList;
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ List authorities = new ArrayList<>();
+ for (String s : roles) {
+ authorities.add(new SimpleGrantedAuthority(s));
+ }
+ return authorities;
+ }
+
+ @Override
+ public String getUsername() {
+ return name;
+ }
}
diff --git a/src/main/java/com/example/nto/entity/Session.java b/src/main/java/com/example/nto/entity/Session.java
new file mode 100644
index 0000000..bf752e6
--- /dev/null
+++ b/src/main/java/com/example/nto/entity/Session.java
@@ -0,0 +1,29 @@
+package com.example.nto.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+import java.util.Date;
+
+@Entity
+@Table(name = "sessions")
+@Data
+public class Session {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+
+ private boolean isExpired = false;
+
+ private String refreshToken;
+
+ private String deviceName;
+
+ @ManyToOne
+ @JoinColumn(name="employee_id", nullable=false)
+ private Employee employee;
+
+ private Date createdAt;
+
+ private Date updatedAt;
+}
diff --git a/src/main/java/com/example/nto/exception/DataValidationException.java b/src/main/java/com/example/nto/exception/DataValidationException.java
new file mode 100644
index 0000000..e38de5a
--- /dev/null
+++ b/src/main/java/com/example/nto/exception/DataValidationException.java
@@ -0,0 +1,7 @@
+package com.example.nto.exception;
+
+public class DataValidationException extends RuntimeException {
+ public DataValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/nto/exception/WrongPasswordException.java b/src/main/java/com/example/nto/exception/WrongPasswordException.java
new file mode 100644
index 0000000..1486301
--- /dev/null
+++ b/src/main/java/com/example/nto/exception/WrongPasswordException.java
@@ -0,0 +1,7 @@
+package com.example.nto.exception;
+
+public class WrongPasswordException extends RuntimeException {
+ public WrongPasswordException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/example/nto/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/example/nto/exception/handler/GlobalExceptionHandler.java
index 1e605cc..e43f48a 100644
--- a/src/main/java/com/example/nto/exception/handler/GlobalExceptionHandler.java
+++ b/src/main/java/com/example/nto/exception/handler/GlobalExceptionHandler.java
@@ -1,8 +1,6 @@
package com.example.nto.exception.handler;
-import com.example.nto.exception.BookingAlreadyExistsException;
-import com.example.nto.exception.EmployeeNotFoundException;
-import com.example.nto.exception.PlaceNotFoundException;
+import com.example.nto.exception.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
@@ -30,4 +28,14 @@ public class GlobalExceptionHandler {
public ResponseEntity handleGenericException(Exception e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
+
+ @ExceptionHandler(DataValidationException.class)
+ public ResponseEntity handleDataValidationException(DataValidationException exception) {
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
+ }
+
+ @ExceptionHandler(WrongPasswordException.class)
+ public ResponseEntity handleWrongPassword(WrongPasswordException exception) {
+ return new ResponseEntity<>(exception.getMessage(), HttpStatus.UNAUTHORIZED);
+ }
}
diff --git a/src/main/java/com/example/nto/repository/EmployeeRepository.java b/src/main/java/com/example/nto/repository/EmployeeRepository.java
index 2ba1c6a..931192e 100644
--- a/src/main/java/com/example/nto/repository/EmployeeRepository.java
+++ b/src/main/java/com/example/nto/repository/EmployeeRepository.java
@@ -4,8 +4,15 @@ import com.example.nto.entity.Employee;
import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+@Repository
public interface EmployeeRepository extends JpaRepository {
- @EntityGraph(attributePaths = {"bookingList", "bookingList.place"})
- Optional findByCode(String code);
+
+ //Optional findByCode(String code);
+
+ // @EntityGraph(attributePaths = {"bookingList", "bookingList.place"})
+ Optional findByName(String name);
+
+ // Optional findBy();
}
diff --git a/src/main/java/com/example/nto/repository/SessionRepository.java b/src/main/java/com/example/nto/repository/SessionRepository.java
new file mode 100644
index 0000000..d030add
--- /dev/null
+++ b/src/main/java/com/example/nto/repository/SessionRepository.java
@@ -0,0 +1,18 @@
+package com.example.nto.repository;
+
+import com.example.nto.entity.Employee;
+import com.example.nto.entity.Session;
+import org.springframework.data.jpa.repository.EntityGraph;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface SessionRepository extends JpaRepository {
+
+ //Optional findByCode(String code);
+
+ //@EntityGraph(attributePaths = {"bookingList", "bookingList.place"})
+ //Optional findByName(String name);
+}
diff --git a/src/main/java/com/example/nto/service/AuthorizationService.java b/src/main/java/com/example/nto/service/AuthorizationService.java
new file mode 100644
index 0000000..ddc13c2
--- /dev/null
+++ b/src/main/java/com/example/nto/service/AuthorizationService.java
@@ -0,0 +1,9 @@
+package com.example.nto.service;
+
+import com.example.nto.controller.dto.CreateAccountDto;
+import com.example.nto.controller.dto.LoginDto;
+
+public interface AuthorizationService {
+ public Object createUser(CreateAccountDto request);
+ public Object loginUser(LoginDto request);
+}
diff --git a/src/main/java/com/example/nto/service/impl/AuthorizationServiceImpl.java b/src/main/java/com/example/nto/service/impl/AuthorizationServiceImpl.java
new file mode 100644
index 0000000..8e82dc4
--- /dev/null
+++ b/src/main/java/com/example/nto/service/impl/AuthorizationServiceImpl.java
@@ -0,0 +1,116 @@
+package com.example.nto.service.impl;
+
+import com.example.nto.controller.dto.CreateAccountDto;
+import com.example.nto.controller.dto.LoginDto;
+import com.example.nto.entity.Employee;
+import com.example.nto.entity.Session;
+import com.example.nto.exception.DataValidationException;
+import com.example.nto.exception.WrongPasswordException;
+import com.example.nto.repository.EmployeeRepository;
+import com.example.nto.repository.SessionRepository;
+import com.example.nto.service.AuthorizationService;
+import com.example.nto.validation.PasswordWithKnownUsernameValidatorService;
+import org.antlr.v4.runtime.misc.Pair;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.sql.Date;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+@Service
+public class AuthorizationServiceImpl implements AuthorizationService {
+
+ private final PasswordWithKnownUsernameValidatorService validatorService;
+ private final EmployeeRepository employeeRepository;
+ private final SessionRepository sessionRepository;
+ private final TokenService tokenService;
+
+
+ public AuthorizationServiceImpl(PasswordWithKnownUsernameValidatorService validatorService, EmployeeRepository employeeRepository, SessionRepository sessionRepository, TokenService tokenService) {
+ this.validatorService = validatorService;
+ this.employeeRepository = employeeRepository;
+ this.sessionRepository = sessionRepository;
+ this.tokenService = tokenService;
+ }
+
+ @Override
+ public Object createUser(CreateAccountDto request) {
+ Pair validationResult = validatorService.isCorrect(request.getUsername(), request.getPassword());
+ if (validationResult.a == false) {
+ throw new DataValidationException(validationResult.b);
+ }
+
+ Employee employee = new Employee();
+ employee.setName(request.getUsername());
+ employee.setPassword(request.getPassword());
+
+ Employee emp = employeeRepository.save(employee);
+
+ Session session = new Session();
+ String refreshToken = tokenService.createRefreshToken();
+ session.setRefreshToken(refreshToken);
+ session.setCreatedAt(Date.from(Instant.now()));
+ session.setUpdatedAt(Date.from(Instant.now()));
+ session.setEmployee(emp);
+ session.setDeviceName(request.getDeviceName());
+
+ sessionRepository.save(session);
+
+ String accessToken = tokenService.generateToken(request.getUsername());
+
+ Map json = new HashMap<>();
+ json.put("access_token", accessToken);
+ json.put("access_token_ttl", 600);
+ json.put("refresh_token", refreshToken);
+
+ Map e = new HashMap<>();
+ e.put("id", emp.getId());
+ e.put("name", emp.getName());
+ e.put("photo_url", emp.getPhotoUrl());
+ e.put("booking_list", emp.getBookingList());
+
+ json.put("employee", e);
+
+ return json;
+ }
+
+ @Override
+ public Object loginUser(LoginDto dto) {
+ Optional employee = employeeRepository.findByName(dto.getUsername());
+ if (employee.isEmpty()) {
+ throw new UsernameNotFoundException("No such user");
+ }
+
+ if (!Objects.equals(employee.get().getPassword(), dto.getPassword())) {
+ throw new WrongPasswordException("Error: wrong password");
+ }
+
+ Session session = new Session();
+ session.setExpired(false);
+ String refreshToken = tokenService.createRefreshToken();
+ session.setRefreshToken(refreshToken);
+ session.setCreatedAt(Date.from(Instant.now()));
+ session.setUpdatedAt(Date.from(Instant.now()));
+ session.setEmployee(employee.get());
+ String accessToken = tokenService.generateToken(dto.getUsername());
+
+ Map json = new HashMap<>();
+ json.put("access_token", accessToken);
+ json.put("access_token_ttl", 600);
+ json.put("refresh_token", refreshToken);
+
+ Map emp = new HashMap<>();
+ emp.put("id", employee.get().getId());
+ emp.put("name", employee.get().getName());
+ emp.put("photo_url", employee.get().getPhotoUrl());
+ emp.put("booking_list", employee.get().getBookingList());
+
+ json.put("employee", emp);
+
+ return json;
+ }
+}
diff --git a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java
index 8c5bccd..8949a6c 100644
--- a/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java
+++ b/src/main/java/com/example/nto/service/impl/BookingServiceImpl.java
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+/*
@Service
@RequiredArgsConstructor
public class BookingServiceImpl implements BookingService {
@@ -112,3 +113,4 @@ public class BookingServiceImpl implements BookingService {
return bookingRepository.save(booking);
}
}
+*/
\ No newline at end of file
diff --git a/src/main/java/com/example/nto/service/impl/EmployeeDetailsService.java b/src/main/java/com/example/nto/service/impl/EmployeeDetailsService.java
new file mode 100644
index 0000000..dc96e33
--- /dev/null
+++ b/src/main/java/com/example/nto/service/impl/EmployeeDetailsService.java
@@ -0,0 +1,30 @@
+package com.example.nto.service.impl;
+
+import com.example.nto.entity.Employee;
+import com.example.nto.repository.EmployeeRepository;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class EmployeeDetailsService implements UserDetailsService {
+
+ private final EmployeeRepository employeeRepository;
+
+ public EmployeeDetailsService(EmployeeRepository employeeRepository) {
+ this.employeeRepository = employeeRepository;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ Optional employee = employeeRepository.findByName(username);
+ if (employee.isEmpty()) {
+ throw new UsernameNotFoundException("User not found with username: " + username);
+ }
+
+ return employee.get();
+ }
+}
diff --git a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java
index fcb6882..2468f54 100644
--- a/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java
+++ b/src/main/java/com/example/nto/service/impl/EmployeeServiceImpl.java
@@ -7,7 +7,7 @@ import com.example.nto.service.EmployeeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-
+/*
@Service
@RequiredArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {
@@ -34,3 +34,4 @@ public class EmployeeServiceImpl implements EmployeeService {
}
}
}
+*/
\ No newline at end of file
diff --git a/src/main/java/com/example/nto/service/impl/TokenService.java b/src/main/java/com/example/nto/service/impl/TokenService.java
new file mode 100644
index 0000000..b90f208
--- /dev/null
+++ b/src/main/java/com/example/nto/service/impl/TokenService.java
@@ -0,0 +1,84 @@
+package com.example.nto.service.impl;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import java.security.Key;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+@Component
+public class TokenService {
+
+ private final String SECRET;
+
+ public TokenService(@Value("${jwt-secret}") String secret) {
+ SECRET = secret;
+ }
+
+ public String generateToken(String email) {
+ Map claims = new HashMap<>();
+ return createToken(claims, email);
+ }
+
+ private String createToken(Map claims, String username) {
+ return Jwts.builder()
+ .setClaims(claims)
+ .setSubject(username)
+ .setIssuedAt(Date.from(Instant.now()))
+ .setExpiration(Date.from(Instant.now().plus(10, ChronoUnit.MINUTES))) // todo: change
+ .signWith(getSignKey(), SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ public String createRefreshToken() {
+ return RandomStringUtils.randomAlphanumeric(64);
+ }
+
+
+ public T extractClaim(String token, Function claimsResolver) {
+ final Claims claims = extractAllClaims(token);
+ return claimsResolver.apply(claims);
+ }
+
+ public Date extractExpiration(String token) {
+ return extractClaim(token, Claims::getExpiration);
+ }
+
+ private Boolean isTokenExpired(String token) {
+ return extractExpiration(token).before(new Date());
+ }
+
+ public String extractUsername(String token) {
+ return extractClaim(token, Claims::getSubject);
+ }
+
+ public Boolean validateToken(String token, UserDetails userDetails) {
+ final String username = extractUsername(token);
+ return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
+ }
+
+ public Claims extractAllClaims(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(getSignKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ private Key getSignKey() {
+ byte[] keyBytes = Decoders.BASE64.decode(SECRET);
+ return Keys.hmacShaKeyFor(keyBytes);
+ }
+}
diff --git a/src/main/java/com/example/nto/validation/PasswordWithKnownUsernameValidatorService.java b/src/main/java/com/example/nto/validation/PasswordWithKnownUsernameValidatorService.java
new file mode 100644
index 0000000..8fcc777
--- /dev/null
+++ b/src/main/java/com/example/nto/validation/PasswordWithKnownUsernameValidatorService.java
@@ -0,0 +1,107 @@
+package com.example.nto.validation;
+
+import org.antlr.v4.runtime.misc.Pair;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class PasswordWithKnownUsernameValidatorService {
+
+ /**
+ * Checks if username and password are correctly validated.
+ * @param username
+ * @param password
+ * @return Pair[Bool, String], if Bool is true String is empty
+ */
+ public Pair isCorrect(String username, String password) {
+ if (username == null) {
+ return new Pair<>(false, "No username provided");
+ }
+ if (password == null) {
+ return new Pair<>(false, "No password provided");
+ }
+ if (username.isEmpty()) {
+ return new Pair<>(false, "Empty username provided");
+ }
+
+ //String rgx = "[^A-Za-z0-9.]";
+ //if (!username.matches(rgx)) {
+ //return new Pair<>(false, "Username does not match regex.");
+ //}
+
+ var check = new boolean[username.length()];
+ for (int i = 0; i < username.length(); i++) {
+ if (username.charAt(i) == '.') {
+ check[i] = true;
+ }
+ if (username.charAt(i) >= 65 && username.charAt(i) <= 90) {
+ check[i] = true;
+ }
+ if (username.charAt(i) >= 97 && username.charAt(i) <= 122) {
+ check[i] = true;
+ }
+ if (username.charAt(i) >= 48 && username.charAt(i) <= 57) {
+ check[i] = true;
+ }
+ }
+
+ for (int i = 0; i < username.length(); i++) {
+ if (!check[i]) {
+ return new Pair<>(false, "Username does not match regex.");
+ }
+ }
+
+ if (password.length() < 8) {
+ return new Pair<>(false, "Password is too short.");
+ }
+
+ Map freq = new HashMap<>();
+ for (int i = 0; i < password.length(); i++) {
+ char c = password.charAt(i);
+ Integer fr = freq.get(c);
+ if (fr == null) {
+ freq.put(c, 1);
+ } else {
+ freq.put(c, fr + 1);
+ }
+ }
+
+ for (Integer count : freq.values()) {
+ if (count >= 3) {
+ return new Pair<>(false, "The same character must not appear 3 or more times.");
+ }
+ }
+
+ String specialSymbols = "!#$%&'()*+,-./:;<=>?@[]^_`{|}~";
+ boolean containsSpecials = false;
+ for (int i = 0; i < password.length(); i++) {
+ for (int j = 0; j < specialSymbols.length(); j++) {
+ // их не так много чтобы заморачиваться с Set
+ if (password.charAt(i) == specialSymbols.charAt(j)) {
+ containsSpecials = true;
+ break;
+ }
+ }
+ }
+
+ if (!containsSpecials) {
+ return new Pair<>(false, "Password should contain one of these special characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~");
+ }
+
+ String passwordLowerCase = password.toLowerCase();
+ String usernameLowerCase = username.toLowerCase();
+ for (int i = 0; i < passwordLowerCase.length() - 2; i++) {
+ for (int j = 0; j < usernameLowerCase.length() - 2; j++) {
+ if (usernameLowerCase.substring(i, i + 2).equals(passwordLowerCase.substring(j, j + 2))) {
+ return new Pair<>(false, "Password must not contain 3 or more continuous characters from provided username");
+ }
+ }
+ }
+
+ return new Pair<>(true, "");
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 8b68191..b57ffec 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -10,7 +10,7 @@ spring:
generate-ddl: false
hibernate:
- ddl-auto: none
+ ddl-auto: create-drop
show-sql: true
@@ -19,4 +19,6 @@ spring:
change-log: classpath:db.changelog/db.changelog-master.xml
booking:
- days-ahead: 3
\ No newline at end of file
+ days-ahead: 3
+
+jwt-secret: FR2TQQ2ZhVTiVR5GuSN4ULBKm8tPiCB9lTZtUxldbUG
diff --git a/src/main/resources/db.changelog/1/0/2025-11-05--0001-employee.xml b/src/main/resources/db.changelog/1/0/2025-11-05--0001-employee.xml
deleted file mode 100644
index d1f92f8..0000000
--- a/src/main/resources/db.changelog/1/0/2025-11-05--0001-employee.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/1/0/2025-11-05--0002-place.xml b/src/main/resources/db.changelog/1/0/2025-11-05--0002-place.xml
deleted file mode 100644
index db4a2b2..0000000
--- a/src/main/resources/db.changelog/1/0/2025-11-05--0002-place.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/1/0/2025-11-05--0003-booking.xml b/src/main/resources/db.changelog/1/0/2025-11-05--0003-booking.xml
deleted file mode 100644
index fa62dce..0000000
--- a/src/main/resources/db.changelog/1/0/2025-11-05--0003-booking.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/2025-11-05--0001-employee-data.xml b/src/main/resources/db.changelog/data/2025-11-05--0001-employee-data.xml
deleted file mode 100644
index 40f8ddb..0000000
--- a/src/main/resources/db.changelog/data/2025-11-05--0001-employee-data.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/2025-11-05--0002-place-data.xml b/src/main/resources/db.changelog/data/2025-11-05--0002-place-data.xml
deleted file mode 100644
index e5351dd..0000000
--- a/src/main/resources/db.changelog/data/2025-11-05--0002-place-data.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/2025-11-05--0003-booking-data.xml b/src/main/resources/db.changelog/data/2025-11-05--0003-booking-data.xml
deleted file mode 100644
index eea0c6b..0000000
--- a/src/main/resources/db.changelog/data/2025-11-05--0003-booking-data.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/csv/2025-11-05--0001-employee-data.csv b/src/main/resources/db.changelog/data/csv/2025-11-05--0001-employee-data.csv
deleted file mode 100644
index 87ddc6b..0000000
--- a/src/main/resources/db.changelog/data/csv/2025-11-05--0001-employee-data.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-name;code;photo_url
-Ivanov Ivan;1111;https://catalog-cdn.detmir.st/media/2fe02057f9915e72a378795d32c79ea9.jpeg
-Petrov Petr;2222;https://catalog-cdn.detmir.st/media/2fe02057f9915e72a378795d32c79ea9.jpeg
-Kozlov Oleg;3333;https://catalog-cdn.detmir.st/media/2fe02057f9915e72a378795d32c79ea9.jpeg
-Smirnova Anna;4444;https://catalog-cdn.detmir.st/media/2fe02057f9915e72a378795d32c79ea9.jpeg
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/csv/2025-11-05--0002-place-data.csv b/src/main/resources/db.changelog/data/csv/2025-11-05--0002-place-data.csv
deleted file mode 100644
index 3354529..0000000
--- a/src/main/resources/db.changelog/data/csv/2025-11-05--0002-place-data.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-place_name
-K-19
-M-16
-T-1
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/data/csv/2025-11-05--0003-booking-data.csv b/src/main/resources/db.changelog/data/csv/2025-11-05--0003-booking-data.csv
deleted file mode 100644
index 11f0364..0000000
--- a/src/main/resources/db.changelog/data/csv/2025-11-05--0003-booking-data.csv
+++ /dev/null
@@ -1,3 +0,0 @@
-date;place_id;employee_id
-2025-11-08;1;1
-2025-11-10;2;2
\ No newline at end of file
diff --git a/src/main/resources/db.changelog/db.changelog-master.xml b/src/main/resources/db.changelog/db.changelog-master.xml
deleted file mode 100644
index 90031f0..0000000
--- a/src/main/resources/db.changelog/db.changelog-master.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/stuff.md b/stuff.md
index b0bbb0d..ab50b02 100644
--- a/stuff.md
+++ b/stuff.md
@@ -1,4 +1,6 @@
Command to build fat jar:
```bash
mvn clean package spring-boot:repackage
-```
\ No newline at end of file
+```
+
+todo: карсиво сделать через аннотацию валидацию всего кроме зависящих юзернейма и пароля (ласт требование)