Skip to content

Commit 07e91a7

Browse files
committed
Refactor authentication, add logout and registration endpoints
1 parent d24c44c commit 07e91a7

File tree

4 files changed

+128
-53
lines changed

4 files changed

+128
-53
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package net.hackyourfuture.coursehub.data;
2+
3+
import org.springframework.security.core.GrantedAuthority;
4+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
5+
import org.springframework.security.core.userdetails.UserDetails;
6+
7+
import java.util.Collection;
8+
import java.util.List;
9+
10+
public record AuthenticatedUser(Integer userId, String firstName, String lastName, String emailAddress, Role role) implements UserDetails {
11+
12+
@Override
13+
public String getUsername() {
14+
return emailAddress;
15+
}
16+
17+
@Override
18+
public String getPassword() {
19+
return null;
20+
}
21+
22+
@Override
23+
public Collection<? extends GrantedAuthority> getAuthorities() {
24+
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
25+
}
26+
}
Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,67 @@
11
package net.hackyourfuture.coursehub.service;
22

3+
import net.hackyourfuture.coursehub.data.AuthenticatedUser;
4+
import net.hackyourfuture.coursehub.data.InstructorEntity;
5+
import net.hackyourfuture.coursehub.data.StudentEntity;
36
import net.hackyourfuture.coursehub.data.UserAccountEntity;
7+
import net.hackyourfuture.coursehub.repository.InstructorRepository;
8+
import net.hackyourfuture.coursehub.repository.StudentRepository;
49
import net.hackyourfuture.coursehub.repository.UserAccountRepository;
5-
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.security.core.context.SecurityContextHolder;
611
import org.springframework.security.core.userdetails.UserDetailsService;
712
import org.springframework.security.core.userdetails.UsernameNotFoundException;
8-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
913
import org.springframework.stereotype.Service;
10-
import java.util.Collections;
1114

1215
@Service
1316
public class UserAuthenticationService implements UserDetailsService {
1417
private final UserAccountRepository userAccountRepository;
18+
private final StudentRepository studentRepository;
19+
private final InstructorRepository instructorRepository;
1520

16-
public UserAuthenticationService(UserAccountRepository userAccountRepository) {
21+
public UserAuthenticationService(
22+
UserAccountRepository userAccountRepository,
23+
StudentRepository studentRepository,
24+
InstructorRepository instructorRepository
25+
) {
1726
this.userAccountRepository = userAccountRepository;
27+
this.studentRepository = studentRepository;
28+
this.instructorRepository = instructorRepository;
1829
}
1930

2031
@Override
21-
public UserDetails loadUserByUsername(String emailAddress) throws UsernameNotFoundException {
32+
public AuthenticatedUser loadUserByUsername(String emailAddress) throws UsernameNotFoundException {
2233
UserAccountEntity user = userAccountRepository.findByEmailAddress(emailAddress);
2334
if (user == null) {
2435
throw new UsernameNotFoundException("No user found for provided emailAddress address");
2536
}
26-
return new org.springframework.security.core.userdetails.User(
27-
user.emailAddress(),
28-
user.passwordHash(),
29-
Collections.singletonList(
30-
new SimpleGrantedAuthority("ROLE_" + user.role().name())));
37+
38+
return switch (user.role()) {
39+
case student -> {
40+
StudentEntity student = studentRepository.findById(user.userId());
41+
yield new AuthenticatedUser(user.userId(), student.firstName(), student.lastName(), user.emailAddress(), user.role());
42+
}
43+
case instructor -> {
44+
InstructorEntity instructor = instructorRepository.findById(user.userId());
45+
yield new AuthenticatedUser(user.userId(), instructor.firstName(), instructor.lastName(), user.emailAddress(), user.role());
46+
}
47+
};
48+
}
49+
50+
public AuthenticatedUser currentAuthenticatedUser() {
51+
var authentication = SecurityContextHolder.getContext().getAuthentication();
52+
if (authentication != null && authentication.isAuthenticated()) {
53+
var principal = authentication.getPrincipal();
54+
if (principal instanceof AuthenticatedUser user) {
55+
return user;
56+
}
57+
}
58+
throw new IllegalStateException("No AuthenticatedUser");
59+
}
60+
61+
public void register(String firstName, String lastName, String emailAddress, String password) {
62+
// For simplicity, we will register every new user as a student
63+
// In a real-world application, you might want to allow registering as an instructor as well
64+
// and have an admin approve instructor accounts before they can log in
65+
studentRepository.insertStudent(firstName, lastName, emailAddress, password);
3166
}
3267
}
Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package net.hackyourfuture.coursehub.web;
22

33
import jakarta.servlet.http.HttpServletRequest;
4-
import net.hackyourfuture.coursehub.data.InstructorEntity;
5-
import net.hackyourfuture.coursehub.data.StudentEntity;
6-
import net.hackyourfuture.coursehub.data.UserAccountEntity;
74
import net.hackyourfuture.coursehub.repository.InstructorRepository;
85
import net.hackyourfuture.coursehub.repository.StudentRepository;
9-
import net.hackyourfuture.coursehub.repository.UserAccountRepository;
6+
import net.hackyourfuture.coursehub.service.UserAuthenticationService;
107
import net.hackyourfuture.coursehub.web.model.HttpErrorResponse;
118
import net.hackyourfuture.coursehub.web.model.LoginRequest;
129
import net.hackyourfuture.coursehub.web.model.LoginSuccessResponse;
10+
import net.hackyourfuture.coursehub.web.model.RegisterRequest;
1311
import org.springframework.http.HttpStatus;
1412
import org.springframework.http.ResponseEntity;
1513
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
@@ -28,55 +26,23 @@
2826
@RestController
2927
public class UserAuthenticationController {
3028
private final AuthenticationManager authenticationManager;
31-
private final UserAccountRepository userAccountRepository;
29+
private final UserAuthenticationService userAuthenticationService;
3230
private final StudentRepository studentRepository;
33-
private final InstructorRepository instructorRepository;
3431

3532
public UserAuthenticationController(
3633
AuthenticationManager authenticationManager,
37-
UserAccountRepository userAccountRepository,
38-
StudentRepository studentRepository,
39-
InstructorRepository instructorRepository) {
34+
UserAuthenticationService userAuthenticationService,
35+
StudentRepository studentRepository) {
4036
this.authenticationManager = authenticationManager;
41-
this.userAccountRepository = userAccountRepository;
37+
this.userAuthenticationService = userAuthenticationService;
4238
this.studentRepository = studentRepository;
43-
this.instructorRepository = instructorRepository;
4439
}
4540

4641
@PostMapping("/login")
4742
public ResponseEntity<Object> login(@RequestBody LoginRequest request, HttpServletRequest httpRequest) {
4843
try {
49-
// Authenticate the user with the provided credentials (email and password)
50-
Authentication authentication = authenticationManager.authenticate(
51-
new UsernamePasswordAuthenticationToken(request.emailAddress(), request.password()));
52-
// Save the authenticated user in the Spring security context
53-
SecurityContextHolder.getContext().setAuthentication(authentication);
54-
// Ensure a session is created for the authenticated user
55-
httpRequest.getSession(true);
56-
57-
// Retrieve the corresponding user data from the database to return
58-
UserAccountEntity user = userAccountRepository.findByEmailAddress(request.emailAddress());
59-
if (user == null) {
60-
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
61-
.body(new HttpErrorResponse("No user found for provided email address"));
62-
}
63-
64-
String firstName = null;
65-
String lastName = null;
66-
switch (user.role()) {
67-
case student -> {
68-
StudentEntity student = studentRepository.findById(user.userId());
69-
firstName = student.firstName();
70-
lastName = student.lastName();
71-
}
72-
case instructor -> {
73-
InstructorEntity instructor = instructorRepository.findById(user.userId());
74-
firstName = instructor.firstName();
75-
lastName = instructor.lastName();
76-
}
77-
}
78-
return ResponseEntity.ok(
79-
new LoginSuccessResponse(user.userId(), firstName, lastName, user.emailAddress(), user.role()));
44+
var response = authenticate(httpRequest, request.emailAddress(), request.password());
45+
return ResponseEntity.ok(response);
8046
} catch (AuthenticationException e) {
8147
if (e instanceof BadCredentialsException) {
8248
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
@@ -90,4 +56,43 @@ public ResponseEntity<Object> login(@RequestBody LoginRequest request, HttpServl
9056
.body(new HttpErrorResponse("Something went wrong"));
9157
}
9258
}
93-
}
59+
60+
@PostMapping("/logout")
61+
public ResponseEntity<?> logout(HttpServletRequest httpRequest) {
62+
SecurityContextHolder.clearContext();
63+
httpRequest.getSession().invalidate();
64+
return ResponseEntity.ok().build();
65+
}
66+
67+
@PostMapping("/register")
68+
public LoginSuccessResponse register(@RequestBody RegisterRequest request, HttpServletRequest httpRequest) {
69+
userAuthenticationService.register(
70+
request.firstName(),
71+
request.lastName(),
72+
request.emailAddress(),
73+
request.password()
74+
);
75+
76+
return authenticate(httpRequest, request.emailAddress(), request.password());
77+
}
78+
79+
private LoginSuccessResponse authenticate(HttpServletRequest httpRequest, String email, String password) {
80+
// Authenticate the user with the provided credentials (email and password)
81+
Authentication authentication = authenticationManager.authenticate(
82+
new UsernamePasswordAuthenticationToken(email, password));
83+
// Save the authenticated user in the Spring security context
84+
SecurityContextHolder.getContext().setAuthentication(authentication);
85+
// Ensure a session is created for the authenticated user
86+
httpRequest.getSession(true);
87+
88+
// Retrieve the corresponding user data to return in a login response
89+
var authenticatedUser = userAuthenticationService.currentAuthenticatedUser();
90+
return new LoginSuccessResponse(
91+
authenticatedUser.userId(),
92+
authenticatedUser.firstName(),
93+
authenticatedUser.lastName(),
94+
authenticatedUser.emailAddress(),
95+
authenticatedUser.role()
96+
);
97+
}
98+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package net.hackyourfuture.coursehub.web.model;
2+
3+
public record RegisterRequest(
4+
String firstName,
5+
String lastName,
6+
String emailAddress,
7+
String password
8+
) {
9+
}

0 commit comments

Comments
 (0)