Files
mealprep/backend/src/main/java/com/recipeapp/auth/AuthService.java
Marcel Raddatz 0b182a33fd refactor(auth): extract authenticateInSession to AuthService
Remove duplicated private authenticateInSession from AuthController and
HouseholdController. Add a single public implementation on AuthService
with session fixation protection built in. HouseholdController now
injects AuthService and passes role "user" for invite-accepted accounts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:24:58 +02:00

122 lines
5.5 KiB
Java

package com.recipeapp.auth;
import com.recipeapp.auth.dto.*;
import com.recipeapp.auth.entity.UserAccount;
import com.recipeapp.common.ConflictException;
import com.recipeapp.common.ResourceNotFoundException;
import com.recipeapp.common.ValidationException;
import com.recipeapp.household.HouseholdMemberRepository;
import com.recipeapp.household.entity.HouseholdMember;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class AuthService {
private final UserAccountRepository userAccountRepository;
private final HouseholdMemberRepository householdMemberRepository;
private final PasswordEncoder passwordEncoder;
public AuthService(UserAccountRepository userAccountRepository,
HouseholdMemberRepository householdMemberRepository,
PasswordEncoder passwordEncoder) {
this.userAccountRepository = userAccountRepository;
this.householdMemberRepository = householdMemberRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public UserResponse signup(SignupRequest request) {
if (userAccountRepository.existsByEmailIgnoreCase(request.email())) {
throw new ConflictException("Email already registered");
}
var user = new UserAccount(
request.email(),
request.displayName(),
passwordEncoder.encode(request.password())
);
user = userAccountRepository.save(user);
return UserResponse.basic(user.getId(), user.getEmail(), user.getDisplayName());
}
@Transactional(readOnly = true)
public UserResponse login(LoginRequest request) {
UserAccount user = userAccountRepository.findByEmailIgnoreCase(request.email())
.orElseThrow(() -> new ResourceNotFoundException("Invalid email or password"));
if (!user.isActive()) {
throw new ValidationException("Account is deactivated");
}
if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) {
throw new ValidationException("Invalid email or password");
}
return toUserResponse(user);
}
@Transactional(readOnly = true)
public UserResponse getCurrentUser(String email) {
UserAccount user = userAccountRepository.findByEmailIgnoreCase(email)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
return toUserResponse(user);
}
@Transactional
public UserResponse updateProfile(String email, UpdateProfileRequest request) {
UserAccount user = userAccountRepository.findByEmailIgnoreCase(email)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
if (request.displayName() != null) {
user.setDisplayName(request.displayName());
}
if (request.newPassword() != null) {
if (request.currentPassword() == null) {
throw new ValidationException("Current password is required to set a new password");
}
if (!passwordEncoder.matches(request.currentPassword(), user.getPasswordHash())) {
throw new ValidationException("Current password is incorrect");
}
user.setPasswordHash(passwordEncoder.encode(request.newPassword()));
}
user = userAccountRepository.save(user);
return UserResponse.basic(user.getId(), user.getEmail(), user.getDisplayName());
}
/**
* Establishes an authenticated Spring Security session for the given user.
* Invalidates any existing session first (session fixation protection).
*/
public void authenticateInSession(String email, String role, HttpServletRequest request) {
var oldSession = request.getSession(false);
if (oldSession != null) {
oldSession.invalidate();
}
var auth = UsernamePasswordAuthenticationToken.authenticated(
email, null, List.of(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
request.getSession(true).setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
}
private UserResponse toUserResponse(UserAccount user) {
return householdMemberRepository.findByUserEmailIgnoreCase(user.getEmail())
.map(member -> UserResponse.withHousehold(
user.getId(), user.getEmail(), user.getDisplayName(),
member.getHousehold().getId(), member.getHousehold().getName(),
member.getRole(), user.getSystemRole()))
.orElse(UserResponse.withHousehold(
user.getId(), user.getEmail(), user.getDisplayName(),
null, null, null, user.getSystemRole()));
}
}