Get user by login; Codes; acs/open endpoint

This commit is contained in:
Universall 2025-02-18 20:26:38 +03:00
parent 126c8e0cb0
commit 15320350e3
24 changed files with 167 additions and 91 deletions

View File

@ -0,0 +1,26 @@
package com.displaynone.acss.components.acs;
import com.displaynone.acss.components.acs.code.CodeModel;
import com.displaynone.acss.components.acs.code.service.CodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class ACSComponent {
private final CodeService codeService;
public Optional<CodeModel> getCodeByValue(Long value) {
return codeService.getCodeByValue(value);
}
public CodeModel getCodeByValueStrict(Long value) {
return codeService.getCodeByValueStrict(value);
}
public boolean isValid(Long code) {
return codeService.isValid(code);
}
}

View File

@ -0,0 +1,10 @@
package com.displaynone.acss.components.acs.code;
import lombok.Data;
@Data
public class CodeDTO {
private Long id;
private Long codeValue;
}

View File

@ -1,4 +1,4 @@
package com.displaynone.acss.components.auth.models.code;
package com.displaynone.acss.components.acs.code;
import com.displaynone.acss.components.auth.models.role.RoleDTO;
import com.displaynone.acss.components.auth.models.role.RoleMapper;
@ -15,7 +15,7 @@ public class CodeMapper {
CodeDTO dto = new CodeDTO();
dto.setId(model.getId());
dto.setValue(model.getValue());
dto.setCodeValue(model.getCodeValue());
return dto;
}

View File

@ -1,9 +1,6 @@
package com.displaynone.acss.components.auth.models.code;
package com.displaynone.acss.components.acs.code;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -12,11 +9,12 @@ import lombok.NoArgsConstructor;
@Entity
@Data
@Builder
@Table(name = "codes")
@NoArgsConstructor
@AllArgsConstructor
public class CodeModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long value;
private long codeValue;
}

View File

@ -1,6 +1,9 @@
package com.displaynone.acss.components.auth.models.code;
package com.displaynone.acss.components.acs.code;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface CodeRepository extends JpaRepository<CodeModel, Long> {
Optional<CodeModel> findByCodeValue(Long codeValue);
}

View File

@ -0,0 +1,13 @@
package com.displaynone.acss.components.acs.code.service;
import com.displaynone.acss.components.acs.code.CodeModel;
import java.util.Optional;
public interface CodeService {
Optional<CodeModel> getCodeByValue(Long value);
CodeModel getCodeByValueStrict(Long value);
boolean isValid(Long code);
}

View File

@ -0,0 +1,33 @@
package com.displaynone.acss.components.acs.code.service;
import com.displaynone.acss.components.acs.code.CodeModel;
import com.displaynone.acss.components.acs.code.CodeRepository;
import com.displaynone.acss.exception.generics.NotFoundHTTPException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class CodeServiceImpl implements CodeService {
private final CodeRepository codeRepository;
@Override
public Optional<CodeModel> getCodeByValue(Long value) {
return codeRepository.findByCodeValue(value);
}
@Override
public CodeModel getCodeByValueStrict(Long value) {
Optional<CodeModel> model = getCodeByValue(value);
if (model.isEmpty()) throw new NotFoundHTTPException("Code not found");
return model.get();
}
@Override
public boolean isValid(Long code) {
return getCodeByValue(code).isPresent();
}
}

View File

@ -2,8 +2,6 @@ package com.displaynone.acss.components.auth;
import com.displaynone.acss.components.auth.internal_utils.JWTUtils;
import com.displaynone.acss.components.auth.models.AuthTokenPair;
import com.displaynone.acss.components.auth.models.user.UserDTO;
import com.displaynone.acss.components.auth.models.user.UserMapper;
import com.displaynone.acss.components.auth.models.user.UserModel;
import com.displaynone.acss.components.auth.models.user.service.UserService;
import com.displaynone.acss.exception.generics.UnauthorizedHTTPException;
@ -31,18 +29,18 @@ public class AuthComponent implements UserDetailsService {
this.userService = userService;
}
private AuthTokenPair _generateTokenPair(@NonNull UserDetails user) {
public AuthTokenPair _generateTokenPair(@NonNull UserDetails user) {
String accessToken = jwtUtils.generateAccessToken(user);
String refreshToken = jwtUtils.generateRefreshToken(user);
return new AuthTokenPair(accessToken, refreshToken);
}
private Optional<UserModel> _findModelByLogin(@NonNull String login) {
public Optional<UserModel> getUserByLogin(@NonNull String login) {
return userService.findByLogin(login);
}
private UserModel _findModelByLoginStrict(@NonNull String login) {
public UserModel getUserByLoginStrict(@NonNull String login) {
return userService.findByLoginStrict(login);
}
@ -50,7 +48,7 @@ public class AuthComponent implements UserDetailsService {
if (!jwtUtils.validateToken(accessToken)) throw new UnauthorizedHTTPException("Invalid access token");
String username = jwtUtils.getLogin(accessToken);
UserModel userModel = _findModelByLoginStrict(username);
UserModel userModel = getUserByLoginStrict(username);
return new UsernamePasswordAuthenticationToken(userModel, null, userModel.getAuthorities());
}
@ -60,22 +58,13 @@ public class AuthComponent implements UserDetailsService {
String login = jwtUtils.getLogin(refreshToken);
System.out.println(login);
UserModel userModel = _findModelByLoginStrict(login);
UserModel userModel = getUserByLoginStrict(login);
return _generateTokenPair(userModel);
}
public Optional<UserDTO> findByLogin(@NonNull String login) {
Optional<UserModel> userModel = _findModelByLogin(login);
return userModel.map(UserMapper::convertToDTO);
}
public UserDTO findByLoginStrict(@NonNull String login) {
return UserMapper.convertToDTO(userService.findByLoginStrict(login));
}
public Pair<UserModel, AuthTokenPair> authenticate(@NonNull String login) {
UserModel userModel = _findModelByLoginStrict(login);
UserModel userModel = getUserByLoginStrict(login);
return Pair.of(userModel, authenticate(userModel));
}
@ -85,6 +74,6 @@ public class AuthComponent implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException {
return _findModelByLoginStrict(username);
return getUserByLoginStrict(username);
}
}

View File

@ -1,10 +0,0 @@
package com.displaynone.acss.components.auth.models.code;
import lombok.Data;
@Data
public class CodeDTO {
private Long id;
private Long value;
}

View File

@ -1,9 +0,0 @@
package com.displaynone.acss.components.auth.models.code.service;
import com.displaynone.acss.components.auth.models.code.CodeModel;
public interface CodeService {
CodeModel getCodeByValue(Long value);
boolean isValid(Long code);
}

View File

@ -1,23 +0,0 @@
package com.displaynone.acss.components.auth.models.code.service;
import com.displaynone.acss.components.auth.models.code.CodeModel;
import com.displaynone.acss.components.auth.models.code.CodeRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CodeServiceImpl implements CodeService {
private final CodeRepository codeRepository;
@Override
public CodeModel getCodeByValue(Long value) {
return null;
}
@Override
public boolean isValid(Long code) {
return false;
}
}

View File

@ -1,13 +1,24 @@
package com.displaynone.acss.controllers.acs;
import com.displaynone.acss.components.acs.ACSComponent;
import com.displaynone.acss.exception.generics.ForbiddenHTTPException;
import lombok.RequiredArgsConstructor;
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/acs")
@RequiredArgsConstructor
public class ACSController {
private final ACSComponent acsComponent;
@PostMapping("/open")
public ResponseEntity<Void> open() {}
public ResponseEntity<Void> open(@RequestBody OpenRB body) {
Long code = body.getCode();
if (!acsComponent.isValid(code)) throw new ForbiddenHTTPException("Invalid code");
return ResponseEntity.ok(null);
}
}

View File

@ -0,0 +1,8 @@
package com.displaynone.acss.controllers.acs;
import lombok.Data;
@Data
public class OpenRB {
private Long code;
}

View File

@ -2,13 +2,13 @@ package com.displaynone.acss.controllers.auth;
import com.displaynone.acss.components.auth.AuthComponent;
import com.displaynone.acss.components.auth.models.AuthTokenPair;
import com.displaynone.acss.components.auth.models.user.UserDTO;
import com.displaynone.acss.components.auth.models.user.UserModel;
import com.displaynone.acss.exception.generics.BadRequestHTTPException;
import com.displaynone.acss.exception.generics.NotFoundHTTPException;
import com.displaynone.acss.exception.generics.UnauthorizedHTTPException;
import com.displaynone.acss.utils.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -17,17 +17,13 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthComponent authComponent;
@Autowired
public AuthController(AuthComponent authComponent) {
this.authComponent = authComponent;
}
@PostMapping("/login")
public ResponseEntity<AuthTokenPair> login(@RequestBody LoginRequestBody loginRequest) {
String login = loginRequest.getLogin();
public ResponseEntity<AuthTokenPair> login(@RequestBody LoginRB body) {
String login = body.getLogin();
if (login == null) throw new BadRequestHTTPException("Login not specified");
Pair<UserModel, AuthTokenPair> authInfo;
@ -42,8 +38,8 @@ public class AuthController {
}
@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody TokenRefreshRequestBody request) {
String requestRefreshToken = request.getRefreshToken();
public ResponseEntity<?> refreshToken(@RequestBody TokenRefreshRB body) {
String requestRefreshToken = body.getRefreshToken();
if (requestRefreshToken == null) throw new BadRequestHTTPException("Refresh token not specified");
AuthTokenPair authTokenPair;

View File

@ -3,6 +3,6 @@ package com.displaynone.acss.controllers.auth;
import lombok.Data;
@Data
public class LoginRequestBody {
public class LoginRB {
private String login;
}

View File

@ -3,6 +3,6 @@ package com.displaynone.acss.controllers.auth;
import lombok.Data;
@Data
public class TokenRefreshRequestBody {
public class TokenRefreshRB {
public String refreshToken;
}

View File

@ -1,17 +1,23 @@
package com.displaynone.acss.controllers.user;
import com.displaynone.acss.components.auth.AuthComponent;
import com.displaynone.acss.components.auth.models.user.UserDTO;
import com.displaynone.acss.components.auth.models.user.UserMapper;
import com.displaynone.acss.components.auth.models.user.UserModel;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final AuthComponent authComponent;
@GetMapping("/me")
public ResponseEntity<UserDTO> getMe() {
UserModel user = (UserModel) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
@ -19,7 +25,8 @@ public class UserController {
}
@GetMapping("/login/{login}")
public ResponseEntity<UserDTO> getUserByLogin() {
// TODO
public ResponseEntity<UserDTO> getUserByLogin(@PathVariable String login) {
UserModel user = authComponent.getUserByLoginStrict(login);
return ResponseEntity.ok(UserMapper.convertToDTO(user));
}
}

View File

@ -0,0 +1,12 @@
package com.displaynone.acss.exception.generics;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.FORBIDDEN)
public class ForbiddenHTTPException extends RuntimeException {
public ForbiddenHTTPException(String message) {
super(message);
}
}

View File

@ -1,10 +1,7 @@
package com.displaynone.acss.exception.handler;
import com.displaynone.acss.exception.*;
import com.displaynone.acss.exception.generics.AlreadyExistsHTTPException;
import com.displaynone.acss.exception.generics.BadRequestHTTPException;
import com.displaynone.acss.exception.generics.NotFoundHTTPException;
import com.displaynone.acss.exception.generics.UnauthorizedHTTPException;
import com.displaynone.acss.exception.generics.*;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,6 +14,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.resource.NoResourceFoundException;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ -73,4 +71,14 @@ public class GlobalExceptionHandler {
return buildErrorResponse(e, HttpStatus.UNAUTHORIZED, "Unauthorized: unexpected error occurred");
}
@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ErrorResponse> handleNoResourceFoundException(NoResourceFoundException e, WebRequest request) {
return buildErrorResponse(e, HttpStatus.NOT_FOUND, null);
}
@ExceptionHandler(ForbiddenHTTPException.class)
public ResponseEntity<ErrorResponse> handleForbiddenHTTPException(ForbiddenHTTPException e, WebRequest request) {
return buildErrorResponse(e, HttpStatus.FORBIDDEN, null);
}
}

View File

@ -46,6 +46,7 @@ public class WebSecurityConfig {
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/users/login/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

View File

@ -9,7 +9,7 @@
<column name="id" type="INTEGER" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="value" type="INTEGER">
<column name="code_value" type="BIGINT">
<constraints unique="true" nullable="false"/>
</column>
</createTable>

View File

@ -1,5 +1,5 @@
user_id;role_id
1;1
1;2
2;2
3;2
4;1

1 user_id role_id
2 1 1 2
3 2 2
4 3 2
5 4 1

View File

@ -1,3 +1,4 @@
id;code_value
1;1234567890123456789
2;9223372036854775807
3;1122334455667788990

1 1 id 1234567890123456789 code_value
1 id code_value
2 1 1 1234567890123456789 1234567890123456789
3 2 2 9223372036854775807 9223372036854775807
4 3 3 1122334455667788990 1122334455667788990

View File

@ -8,8 +8,10 @@
<include file="db/changelog/01/0002-roles.xml"/>
<include file="db/changelog/01/0001-users.xml"/>
<include file="db/changelog/01/0003-user_roles.xml"/>
<include file="db/changelog/01/0004-codes.xml"/>
<include file="db/changelog/data/0002-roles-data.xml"/>
<include file="db/changelog/data/0001-user-data.xml"/>
<include file="db/changelog/data/0003-user_roles-data.xml"/>
<include file="db/changelog/data/0004-codes-data.xml"/>
</databaseChangeLog>