Pre admin
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package net.tokishu.note.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface CurrentUser {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.tokishu.note.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface NullableCurrentUser {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.tokishu.note.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.tokishu.note.resolver.CurrentUserArgumentResolver;
|
||||
import net.tokishu.note.resolver.NullableCurrentUserArgumentResolver;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final CurrentUserArgumentResolver currentUserArgumentResolver;
|
||||
private final NullableCurrentUserArgumentResolver nullableCurrentUserArgumentResolver;
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||
resolvers.add(currentUserArgumentResolver);
|
||||
resolvers.add(nullableCurrentUserArgumentResolver);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import java.util.Map;
|
||||
public class AuthController {
|
||||
|
||||
public final AuthService authService;
|
||||
public final UserService userService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<RegisterResponse> register(@RequestBody @Valid RegisterRequest request) {
|
||||
|
||||
@@ -2,8 +2,11 @@ package net.tokishu.note.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.tokishu.note.annotation.CurrentUser;
|
||||
import net.tokishu.note.annotation.NullableCurrentUser;
|
||||
import net.tokishu.note.dto.request.NoteRequest;
|
||||
import net.tokishu.note.dto.response.NoteResponse;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.service.NoteService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -23,30 +26,30 @@ public class NotesController {
|
||||
private final NoteService noteService;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<NoteResponse>> getAll(){
|
||||
return ResponseEntity.ok(noteService.getAll());
|
||||
public ResponseEntity<List<NoteResponse>> getAll(@CurrentUser User sender){
|
||||
return ResponseEntity.ok(noteService.getAll(sender));
|
||||
}
|
||||
|
||||
@GetMapping("/{idOrCode}")
|
||||
public NoteResponse find(@PathVariable String idOrCode) {
|
||||
return noteService.findByIdOrPublicLink(idOrCode);
|
||||
public NoteResponse find(@PathVariable String idOrCode, @NullableCurrentUser User sender) {
|
||||
return noteService.findByIdOrPublicLink(idOrCode, sender);
|
||||
}
|
||||
|
||||
@PostMapping()
|
||||
public ResponseEntity<Map<String, String>> add(@RequestBody @Valid NoteRequest note){
|
||||
noteService.add(note);
|
||||
public ResponseEntity<Map<String, String>> add(@RequestBody @Valid NoteRequest note, @CurrentUser User sender){
|
||||
noteService.add(note, sender);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Note added"));
|
||||
}
|
||||
|
||||
@PutMapping("/{uuid}")
|
||||
public ResponseEntity<?> update(@PathVariable("uuid") UUID uuid, @RequestBody @Valid NoteRequest note){
|
||||
noteService.update(uuid, note);
|
||||
public ResponseEntity<?> update(@PathVariable("uuid") UUID uuid, @RequestBody @Valid NoteRequest note, @CurrentUser User sender){
|
||||
noteService.update(uuid, note, sender);
|
||||
return ResponseEntity.ok(Map.of("message", "Note updated"));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{uuid}")
|
||||
public ResponseEntity<?> delete(@PathVariable UUID uuid){
|
||||
noteService.delete(uuid);
|
||||
public ResponseEntity<?> delete(@PathVariable UUID uuid, @CurrentUser User sender){
|
||||
noteService.delete(uuid, sender);
|
||||
return ResponseEntity.ok(Map.of("message", "Note deleted"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,79 @@
|
||||
package net.tokishu.note.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Builder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import net.tokishu.note.annotation.CurrentUser;
|
||||
import net.tokishu.note.dto.request.ChangePasswordRequest;
|
||||
import net.tokishu.note.dto.request.UpdateProfileRequest;
|
||||
import net.tokishu.note.dto.request.UserByAdminRequest;
|
||||
import net.tokishu.note.dto.response.ApiResponse;
|
||||
import net.tokishu.note.dto.response.UserResponse;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.service.UserService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping("/me")
|
||||
public UserResponse me(@CurrentUser User sender){
|
||||
return userService.getCurrentUserResponse(sender);
|
||||
}
|
||||
|
||||
@PutMapping("/me")
|
||||
public UserResponse updateMyProfile(@RequestBody @Valid UpdateProfileRequest request, @CurrentUser User sender) {
|
||||
return userService.updateCurrentUser(request, sender);
|
||||
}
|
||||
|
||||
@PutMapping("/me/password")
|
||||
public ApiResponse changePassword(@RequestBody @Valid ChangePasswordRequest request, @CurrentUser User sender) {
|
||||
// userService.changePassword(request);
|
||||
return ApiResponse.builder().status(200).message("JoJ").build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
ADMIN ACTIONS
|
||||
*/
|
||||
|
||||
// @PostMapping("/new")
|
||||
// @PreAuthorize("hasAuthority('ADMIN')")
|
||||
// public ResponseEntity<?> newUser(@RequestBody @Valid UserByAdminRequest request, @CurrentUser User sender){
|
||||
// return userService.createUserByAdmin(request);
|
||||
// }
|
||||
//
|
||||
// @GetMapping
|
||||
// @PreAuthorize("hasAuthority('ADMIN')")
|
||||
// public ResponseEntity<List<UserResponse>> getAllUsers(@CurrentUser User sender) {
|
||||
// return userService.getAllUsers();
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/{username}")
|
||||
// @PreAuthorize("hasAuthority('ADMIN')")
|
||||
// public UserResponse getUserByUsername(@PathVariable String username, @CurrentUser User sender) {
|
||||
// return userService.getUserByUsername(username);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @PutMapping("/{username}")
|
||||
// @PreAuthorize("hasAuthority('ADMIN')")
|
||||
// public UserResponse editUser(@RequestBody @Valid UserByAdminRequest request, @PathVariable String username, @CurrentUser User sender){
|
||||
// return userService.editUser(username, request);
|
||||
// }
|
||||
//
|
||||
// @DeleteMapping("/{username}")
|
||||
// @PreAuthorize("hasAuthority('ADMIN')")
|
||||
// public ApiResponse removeUser(@PathVariable String username, @CurrentUser User sender){
|
||||
// return userService.deleteUser(username);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.tokishu.note.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChangePasswordRequest {
|
||||
@NotBlank
|
||||
private String oldPassword;
|
||||
|
||||
@NotBlank
|
||||
private String newPassword;
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package net.tokishu.note.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RegisterRequest {
|
||||
@NotBlank
|
||||
@Size(min=1, max=24)
|
||||
@NotBlank()
|
||||
@Size(min = 3, max = 24)
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]+$",
|
||||
message = "The username can only contain letters, numbers, symbols and hyphens.")
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
@Size(min=6, max=42)
|
||||
@NotBlank()
|
||||
@Size(min = 6, max = 42)
|
||||
private String password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net.tokishu.note.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateProfileRequest {
|
||||
@NotBlank()
|
||||
@Size(min = 3, max = 24)
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]+$",
|
||||
message = "The username can only contain letters, numbers, symbols and hyphens.")
|
||||
private String username;
|
||||
|
||||
@NotBlank()
|
||||
private String gravatarEmail;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.tokishu.note.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
|
||||
@Data
|
||||
public class UserByAdminRequest {
|
||||
@NotBlank()
|
||||
@Size(min = 3, max = 24)
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]+$",
|
||||
message = "The username can only contain letters, numbers, symbols and hyphens.")
|
||||
private String username;
|
||||
|
||||
@NotBlank()
|
||||
@Size(min = 6, max = 42)
|
||||
private String password;
|
||||
|
||||
@NotBlank
|
||||
private UserRole role;
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class ApiErrorResponse {
|
||||
public class ApiResponse {
|
||||
private final int status;
|
||||
private final String message;
|
||||
}
|
||||
@@ -2,11 +2,12 @@ package net.tokishu.note.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private String username;
|
||||
private String role;
|
||||
private UserRole role;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package net.tokishu.note.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RegisterResponse {
|
||||
private String username;
|
||||
private String role;
|
||||
private UserRole role;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ package net.tokishu.note.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RootResponse {
|
||||
private String username;
|
||||
private String password;
|
||||
private String role;
|
||||
private UserRole role;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package net.tokishu.note.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class UserResponse {
|
||||
private String username;
|
||||
private String role;
|
||||
private String gravatarUrl;
|
||||
private UserRole role;
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package net.tokishu.note.exception;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import net.tokishu.note.dto.response.ApiErrorResponse;
|
||||
import net.tokishu.note.dto.response.ApiResponse;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleAll(Exception ex) {
|
||||
public ResponseEntity<ApiResponse> handleAll(Exception ex) {
|
||||
if (ex instanceof MethodArgumentNotValidException validationEx) {
|
||||
return handleValidation(validationEx);
|
||||
}
|
||||
@@ -49,7 +46,7 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
|
||||
public ResponseEntity<ApiResponse> handleValidation(MethodArgumentNotValidException ex) {
|
||||
String message = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(error -> error.getField() + ": " + error.getDefaultMessage())
|
||||
.findFirst()
|
||||
@@ -59,10 +56,10 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
|
||||
|
||||
private ResponseEntity<ApiErrorResponse> buildErrorResponse(int status, String message) {
|
||||
private ResponseEntity<ApiResponse> buildErrorResponse(int status, String message) {
|
||||
return ResponseEntity
|
||||
.status(status)
|
||||
.body(ApiErrorResponse.builder()
|
||||
.body(ApiResponse.builder()
|
||||
.status(status)
|
||||
.message(message != null ? message : "Unexpected error")
|
||||
.build());
|
||||
|
||||
@@ -2,7 +2,7 @@ package net.tokishu.note.exception;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.tokishu.note.dto.response.ApiErrorResponse;
|
||||
import net.tokishu.note.dto.response.ApiResponse;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
@@ -20,7 +20,7 @@ public class SecurityExceptionHandler implements AuthenticationEntryPoint, Acces
|
||||
private void writeErrorResponse(HttpServletResponse response, int status, String message) throws IOException {
|
||||
response.setStatus(status);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
ApiErrorResponse body = ApiErrorResponse.builder()
|
||||
ApiResponse body = ApiResponse.builder()
|
||||
.status(status)
|
||||
.message(message)
|
||||
.build();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.tokishu.note.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
@@ -13,11 +14,19 @@ import java.util.UUID;
|
||||
@Table(name = "Users")
|
||||
public class User {
|
||||
@Id
|
||||
@Column(length = 24)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ColumnDefault("'USER'")
|
||||
private String role;
|
||||
private UserRole role;
|
||||
|
||||
@Email
|
||||
@Column(nullable = true)
|
||||
private String gravatarEmail;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(updatable = false)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.tokishu.note.model;
|
||||
|
||||
public enum UserRole {
|
||||
ADMIN, USER
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package net.tokishu.note.repo;
|
||||
|
||||
import net.tokishu.note.model.Note;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -12,4 +15,8 @@ public interface NoteRepository extends JpaRepository<Note, UUID> {
|
||||
List<Note> findByAuthorUsernameOrderByCreatedAtDesc(String username);
|
||||
Optional<Note> findByPublicLinkAndIsPublicTrue(String publicLink);
|
||||
boolean existsByPublicLink(String publicLink);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE Note n SET n.author = :newUsername WHERE n.author = :oldUsername")
|
||||
void updateOwnerUsername(@Param("oldUsername") String oldUsername, @Param("newUsername") String newUsername);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package net.tokishu.note.repo;
|
||||
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, String> {
|
||||
boolean existsByRole(String role);
|
||||
boolean existsByRole(UserRole role);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE User u SET u.username = :newUsername WHERE u.username = :oldUsername")
|
||||
void updateUsername(@Param("oldUsername") String oldUsername, @Param("newUsername") String newUsername);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.tokishu.note.resolver;
|
||||
|
||||
import net.tokishu.note.annotation.CurrentUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.service.AuthService;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(CurrentUser.class)
|
||||
&& parameter.getParameterType().equals(User.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) {
|
||||
return authService.getCurrentUser();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.tokishu.note.resolver;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.tokishu.note.annotation.NullableCurrentUser;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.service.AuthService;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class NullableCurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(NullableCurrentUser.class)
|
||||
&& parameter.getParameterType().equals(User.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) {
|
||||
return authService.getCurrentUserOrNull();
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,17 @@ import net.tokishu.note.dto.request.RegisterRequest;
|
||||
import net.tokishu.note.dto.response.LoginResponse;
|
||||
import net.tokishu.note.dto.response.RegisterResponse;
|
||||
import net.tokishu.note.dto.response.RootResponse;
|
||||
import net.tokishu.note.dto.response.UserResponse;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
import net.tokishu.note.repo.UserRepository;
|
||||
import net.tokishu.note.security.JwtService;
|
||||
import net.tokishu.note.util.GravatarUtil;
|
||||
import net.tokishu.note.util.PasswordGenerator;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -19,24 +25,14 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
@RequiredArgsConstructor
|
||||
public class AuthService {
|
||||
|
||||
public final UserRepository userRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
|
||||
public RootResponse registerRoot() {
|
||||
if (userRepository.existsByRole("ADMIN")) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Administrator already exists");
|
||||
}
|
||||
|
||||
String rawPassword = PasswordGenerator.generate(16);
|
||||
String hashedPassword = passwordEncoder.encode(rawPassword);
|
||||
|
||||
User rootUser = new User();
|
||||
rootUser.setUsername("root");
|
||||
rootUser.setPassword(hashedPassword);
|
||||
rootUser.setRole("ADMIN");
|
||||
|
||||
userRepository.save(rootUser);
|
||||
User rootUser = userService.createUser("root", rawPassword, UserRole.ADMIN, null);
|
||||
|
||||
return RootResponse.builder()
|
||||
.username(rootUser.getUsername())
|
||||
@@ -45,18 +41,8 @@ public class AuthService {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public RegisterResponse registerUser(RegisterRequest request) {
|
||||
if (userRepository.existsById(request.getUsername())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
user.setRole("USER");
|
||||
|
||||
userRepository.save(user);
|
||||
User user = userService.createRegularUser(request.getUsername(), request.getPassword());
|
||||
|
||||
return RegisterResponse.builder()
|
||||
.username(user.getUsername())
|
||||
@@ -77,7 +63,7 @@ public class AuthService {
|
||||
org.springframework.security.core.userdetails.User
|
||||
.withUsername(user.getUsername())
|
||||
.password(user.getPassword())
|
||||
.roles(user.getRole())
|
||||
.roles(user.getRole().name())
|
||||
.build()
|
||||
);
|
||||
|
||||
@@ -88,4 +74,46 @@ public class AuthService {
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
public User getCurrentUser() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated");
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username;
|
||||
|
||||
if (principal instanceof UserDetails userDetails) {
|
||||
username = userDetails.getUsername();
|
||||
} else if (principal instanceof String) {
|
||||
username = (String) principal;
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unable to identify user");
|
||||
}
|
||||
|
||||
return userRepository.findById(username)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found"));
|
||||
}
|
||||
|
||||
public User getCurrentUserOrNull() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username;
|
||||
|
||||
if (principal instanceof UserDetails userDetails) {
|
||||
username = userDetails.getUsername();
|
||||
} else if (principal instanceof String) {
|
||||
username = (String) principal;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return userRepository.findById(username).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import net.tokishu.note.dto.request.NoteRequest;
|
||||
import net.tokishu.note.dto.response.NoteResponse;
|
||||
import net.tokishu.note.model.Note;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
import net.tokishu.note.repo.NoteRepository;
|
||||
import net.tokishu.note.util.CheckAuthUtil;
|
||||
import net.tokishu.note.util.CodeGenerator;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -22,11 +24,10 @@ public class NoteService {
|
||||
public final NoteRepository noteRepository;
|
||||
public final UserService userService;
|
||||
|
||||
public List<NoteResponse> getAll() {
|
||||
User currentUser = userService.getCurrentUser();
|
||||
public List<NoteResponse> getAll(User currentUser) {
|
||||
List<Note> notes;
|
||||
|
||||
if ("ADMIN".equalsIgnoreCase(currentUser.getRole())) {
|
||||
if (currentUser.getRole() == UserRole.ADMIN) {
|
||||
notes = noteRepository.findAll();
|
||||
} else {
|
||||
notes = noteRepository.findByAuthorUsername(currentUser.getUsername());
|
||||
@@ -37,18 +38,17 @@ public class NoteService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public NoteResponse findByIdOrPublicLink(String idOrCode) {
|
||||
public NoteResponse findByIdOrPublicLink(String idOrCode, User currentUser) {
|
||||
if (!StringUtils.hasText(idOrCode)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid identifier or public link");
|
||||
}
|
||||
|
||||
Note note;
|
||||
if (isUuid(idOrCode)) {
|
||||
CheckAuthUtil.check(currentUser);
|
||||
UUID id = UUID.fromString(idOrCode);
|
||||
note = noteRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
|
||||
|
||||
User currentUser = userService.getCurrentUser();
|
||||
checkOwnership(note, currentUser);
|
||||
} else {
|
||||
note = noteRepository.findByPublicLinkAndIsPublicTrue(idOrCode)
|
||||
@@ -58,10 +58,7 @@ public class NoteService {
|
||||
return toResponse(note);
|
||||
}
|
||||
|
||||
|
||||
public NoteResponse add(NoteRequest data) {
|
||||
User author = userService.getCurrentUser();
|
||||
|
||||
public NoteResponse add(NoteRequest data, User author) {
|
||||
Note note = new Note();
|
||||
note.setName(data.getName());
|
||||
note.setText(data.getText());
|
||||
@@ -86,12 +83,10 @@ public class NoteService {
|
||||
return toResponse(saved);
|
||||
}
|
||||
|
||||
|
||||
public NoteResponse update(UUID uuid, NoteRequest data) {
|
||||
public NoteResponse update(UUID uuid, NoteRequest data, User currentUser) {
|
||||
Note existing = noteRepository.findById(uuid)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
|
||||
|
||||
User currentUser = userService.getCurrentUser();
|
||||
checkOwnership(existing, currentUser);
|
||||
|
||||
existing.setName(data.getName());
|
||||
@@ -101,17 +96,16 @@ public class NoteService {
|
||||
return toResponse(noteRepository.save(existing));
|
||||
}
|
||||
|
||||
public void delete(UUID uuid) {
|
||||
public void delete(UUID uuid, User currentUser) {
|
||||
Note existing = noteRepository.findById(uuid)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
|
||||
|
||||
User currentUser = userService.getCurrentUser();
|
||||
|
||||
checkOwnership(existing, currentUser);
|
||||
noteRepository.delete(existing);
|
||||
}
|
||||
|
||||
private void checkOwnership(Note note, User user) {
|
||||
if ("ADMIN".equalsIgnoreCase(user.getRole())) { // TODO: use enum
|
||||
if (user.getRole() == UserRole.ADMIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package net.tokishu.note.service;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.tokishu.note.dto.request.RegisterRequest;
|
||||
import net.tokishu.note.dto.request.UpdateProfileRequest;
|
||||
import net.tokishu.note.dto.response.UserResponse;
|
||||
import net.tokishu.note.model.User;
|
||||
import net.tokishu.note.model.UserRole;
|
||||
import net.tokishu.note.repo.NoteRepository;
|
||||
import net.tokishu.note.repo.UserRepository;
|
||||
import net.tokishu.note.util.CheckAuthUtil;
|
||||
import net.tokishu.note.util.GravatarUtil;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -13,45 +18,71 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
public final UserRepository userRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final NoteRepository noteRepository;
|
||||
|
||||
public List<User> getAll(){
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
public User getCurrentUser() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated");
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username;
|
||||
|
||||
if (principal instanceof UserDetails userDetails) {
|
||||
username = userDetails.getUsername();
|
||||
} else if (principal instanceof String) {
|
||||
username = (String) principal;
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unable to identify user");
|
||||
}
|
||||
|
||||
return userRepository.findById(username)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found"));
|
||||
}
|
||||
|
||||
public UserResponse getCurrentUserResponse() {
|
||||
User user = getCurrentUser();
|
||||
public UserResponse getCurrentUserResponse(User user) {
|
||||
return UserResponse.builder()
|
||||
.username(user.getUsername())
|
||||
.role(user.getRole())
|
||||
.gravatarUrl(GravatarUtil.generateGravatarUrl(user.getGravatarEmail()))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
public User createUser(String username, String password, UserRole role, User currentUser) {
|
||||
if (userRepository.existsById(username)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "User already exists");
|
||||
}
|
||||
|
||||
boolean adminExists = userRepository.existsByRole(UserRole.ADMIN);
|
||||
boolean isCreatingAdmin = UserRole.ADMIN.equals(role);
|
||||
|
||||
if (isCreatingAdmin && adminExists) {
|
||||
if (currentUser == null || currentUser.getRole() != UserRole.ADMIN) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Only admin can create another admin");
|
||||
}
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
user.setRole(role);
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public User createRegularUser(String username, String password) {
|
||||
return createUser(username, password, UserRole.USER, null);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserResponse updateCurrentUser(UpdateProfileRequest request, User currentUser) {
|
||||
String oldUsername = currentUser.getUsername();
|
||||
String newUsername = request.getUsername();
|
||||
|
||||
if (!oldUsername.equals(newUsername)) {
|
||||
if (userRepository.existsById(newUsername)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Username is already taken");
|
||||
}
|
||||
|
||||
userRepository.updateUsername(oldUsername, newUsername);
|
||||
noteRepository.updateOwnerUsername(oldUsername, newUsername);
|
||||
|
||||
currentUser.setUsername(newUsername);
|
||||
}
|
||||
|
||||
currentUser.setGravatarEmail(request.getGravatarEmail());
|
||||
User saved = userRepository.save(currentUser);
|
||||
|
||||
return UserResponse.builder()
|
||||
.username(saved.getUsername())
|
||||
.role(currentUser.getRole())
|
||||
.gravatarUrl(GravatarUtil.generateGravatarUrl(saved.getGravatarEmail()))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.tokishu.note.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.tokishu.note.model.User;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@UtilityClass
|
||||
public class CheckAuthUtil {
|
||||
public void check(User user) {
|
||||
if (user == null) {
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.tokishu.note.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class GravatarUtil {
|
||||
|
||||
public static String generateGravatarUrl(String email) {
|
||||
if (email == null || email.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md.digest(email.trim().toLowerCase().getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (byte b : digest) {
|
||||
sb.append(String.format("%02x", b & 0xff));
|
||||
}
|
||||
|
||||
return "https://www.gravatar.com/avatar/" + sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("MD5 not supported", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user