Added users logic to the NoteService

Fix password leak by introducing NoteResponse DTO
Fix typo: "serurity" → "security"
This commit is contained in:
Tokishu
2025-05-31 02:10:11 +02:00
parent 078ff4fb41
commit e86b8bf08e
10 changed files with 317 additions and 211 deletions
@@ -5,7 +5,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.serurity.JwtService;
import net.tokishu.note.seсurity.JwtService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
@@ -2,13 +2,13 @@ package net.tokishu.note.controller;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.dto.request.NoteRequest;
import net.tokishu.note.model.Note;
import net.tokishu.note.dto.response.NoteResponse;
import net.tokishu.note.service.NoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -20,17 +20,17 @@ public class NotesController {
private final NoteService noteService;
@GetMapping
public ResponseEntity<?> getAll(){
public ResponseEntity<List<NoteResponse>> getAll(){
return ResponseEntity.ok(noteService.getAll());
}
@GetMapping("/{uuid}")
public Note find(@PathVariable UUID uuid){
public NoteResponse find(@PathVariable UUID uuid){
return noteService.find(uuid);
}
@PostMapping()
public ResponseEntity<?> add(@RequestBody NoteRequest note){
public ResponseEntity<Map<String, String>> add(@RequestBody NoteRequest note){
noteService.add(note);
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Note added"));
}
@@ -44,6 +44,6 @@ public class NotesController {
@DeleteMapping("/{uuid}")
public ResponseEntity<?> delete(@PathVariable UUID uuid){
noteService.delete(uuid);
return ResponseEntity.ok(Map.of(" message", "Note deleted"));
return ResponseEntity.ok(Map.of("message", "Note deleted"));
}
}
@@ -0,0 +1,17 @@
package net.tokishu.note.dto.response;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.UUID;
@Data
@Builder
public class NoteResponse {
private UUID uuid;
private String name;
private String text;
private String author;
private LocalDateTime createdAt;
}
@@ -8,8 +8,8 @@ import net.tokishu.note.dto.response.RegisterResponse;
import net.tokishu.note.dto.response.RootResponse;
import net.tokishu.note.model.User;
import net.tokishu.note.repo.UserRepository;
import net.tokishu.note.serurity.JwtService;
import net.tokishu.note.serurity.PasswordService;
import net.tokishu.note.seсurity.JwtService;
import net.tokishu.note.seсurity.PasswordService;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -2,50 +2,101 @@ package net.tokishu.note.service;
import lombok.RequiredArgsConstructor;
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.repo.NoteRepository;
import net.tokishu.note.repo.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class NoteService {
public final NoteRepository noteRepository;
public final UserService userService;
public final UserRepository userRepository;
public List<Note> getAll(){
return noteRepository.findAll();
public List<NoteResponse> getAll() {
User currentUser = userService.getCurrentUser();
List<Note> notes;
if ("ADMIN".equalsIgnoreCase(currentUser.getRole())) {
notes = noteRepository.findAll();
} else {
notes = noteRepository.findByAuthorUsername(currentUser.getUsername());
}
return notes.stream()
.map(this::toResponse)
.collect(Collectors.toList());
}
public Note find(UUID uuid) {
return noteRepository.findById(uuid)
public NoteResponse find(UUID uuid) {
Note note = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
User currentUser = userService.getCurrentUser();
checkOwnership(note, currentUser);
return toResponse(note);
}
public NoteResponse add(NoteRequest data){
User author = userService.getCurrentUser();
public Note add(NoteRequest data){
Note note = new Note();
note.setName(data.getName());
note.setText(data.getText());
note.setName(data.getName());
note.setText(data.getText());
note.setAuthor(author);
return noteRepository.save(note);
Note saved = noteRepository.save(note);
return toResponse(saved);
}
public Note update(UUID uuid, NoteRequest data){
public NoteResponse update(UUID uuid, NoteRequest data) {
Note existing = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
existing.setName(data.getName());
existing.setText(data.getText());
return noteRepository.save(existing);
User currentUser = userService.getCurrentUser();
checkOwnership(existing, currentUser);
existing.setName(data.getName());
existing.setText(data.getText());
return toResponse(noteRepository.save(existing));
}
public void delete(UUID uuid){
public void delete(UUID uuid) {
Note existing = noteRepository.findById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Note not found"));
noteRepository.deleteById(uuid);
User currentUser = userService.getCurrentUser();
checkOwnership(existing, currentUser);
noteRepository.delete(existing);
}
private void checkOwnership(Note note, User user) {
if ("ADMIN".equalsIgnoreCase(user.getRole())) {
return;
}
if (!note.getAuthor().getUsername().equals(user.getUsername())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You are not the author of this note");
}
}
private NoteResponse toResponse(Note note) {
return NoteResponse.builder()
.uuid(note.getUuid())
.name(note.getName())
.text(note.getText())
.author(note.getAuthor().getUsername())
.createdAt(note.getCreatedAt())
.build();
}
}
@@ -2,9 +2,13 @@ package net.tokishu.note.service;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.dto.request.RegisterRequest;
import net.tokishu.note.dto.response.UserResponse;
import net.tokishu.note.model.User;
import net.tokishu.note.repo.UserRepository;
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,4 +23,35 @@ public class UserService {
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();
return UserResponse.builder()
.username(user.getUsername())
.role(user.getRole())
.build();
}
}
@@ -1,4 +1,4 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import lombok.RequiredArgsConstructor;
import net.tokishu.note.model.User;
@@ -1,13 +1,16 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.security.Key;
import java.util.Date;
@@ -49,14 +52,20 @@ public class JwtService {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
// JwtService.java
public Claims extractAllClaims(String token) {
try {
return Jwts.parser()
.setSigningKey(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
} catch (ExpiredJwtException e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Token expired");
}
}
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
@@ -1,4 +1,4 @@
package net.tokishu.note.serurity;
package net.tokishu.note.seсurity;
import org.springframework.stereotype.Service;