-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUserAuthenticationService.java
More file actions
129 lines (114 loc) · 5.81 KB
/
UserAuthenticationService.java
File metadata and controls
129 lines (114 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package net.hackyourfuture.coursehub.service;
import net.hackyourfuture.coursehub.data.AuthenticatedUser;
import net.hackyourfuture.coursehub.data.InstructorEntity;
import net.hackyourfuture.coursehub.data.StudentEntity;
import net.hackyourfuture.coursehub.data.UserAccountEntity;
import net.hackyourfuture.coursehub.repository.InstructorRepository;
import net.hackyourfuture.coursehub.repository.StudentRepository;
import net.hackyourfuture.coursehub.repository.UserAccountRepository;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
@Service
public class UserAuthenticationService implements UserDetailsService {
private final UserAccountRepository userAccountRepository;
private final StudentRepository studentRepository;
private final InstructorRepository instructorRepository;
private final PasswordEncoder passwordEncoder;
public UserAuthenticationService(
UserAccountRepository userAccountRepository,
StudentRepository studentRepository,
InstructorRepository instructorRepository,
PasswordEncoder passwordEncoder
) {
this.userAccountRepository = userAccountRepository;
this.studentRepository = studentRepository;
this.instructorRepository = instructorRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public AuthenticatedUser loadUserByUsername(String emailAddress) throws UsernameNotFoundException {
UserAccountEntity user = userAccountRepository.findByEmailAddress(emailAddress);
if (user == null) {
throw new UsernameNotFoundException("No user found for provided emailAddress address");
}
return switch (user.role()) {
case student -> {
StudentEntity student = studentRepository.findById(user.userId());
yield new AuthenticatedUser(user.userId(), student.firstName(), student.lastName(), user.emailAddress(), user.passwordHash(), user.role());
}
case instructor -> {
InstructorEntity instructor = instructorRepository.findById(user.userId());
yield new AuthenticatedUser(user.userId(), instructor.firstName(), instructor.lastName(), user.emailAddress(), user.passwordHash(), user.role());
}
};
}
public AuthenticatedUser currentAuthenticatedUser() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
var principal = authentication.getPrincipal();
if (principal instanceof AuthenticatedUser user) {
return user;
}
}
throw new IllegalStateException("No AuthenticatedUser");
}
public void register(String firstName, String lastName, String emailAddress, String password) {
// For simplicity, we will register every new user as a student
// In a real-world application, you might want to allow registering as an instructor as well
// and have an admin approve instructor accounts before they can log in
var passwordHash = passwordEncoder.encode(password);
studentRepository.insertStudent(firstName, lastName, emailAddress, passwordHash);
}
/**
* Generates a new API key for the current authenticated user.
* @return the generated API key
* @throws IllegalStateException if no user is authenticated
*/
public String generateApiKey() {
AuthenticatedUser authenticatedUser = currentAuthenticatedUser();
if (authenticatedUser == null) {
throw new IllegalStateException("No authenticated user found");
}
// Generate a secure random API key with a prefix to identify it as an API key
byte[] randomBytes = new byte[32];
try {
SecureRandom.getInstanceStrong().nextBytes(randomBytes);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to generate an API key", e);
}
String apiKey = "chub_" + Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
// Store the API key in the database
userAccountRepository.updateApiKey(authenticatedUser.getUserId(), apiKey);
return apiKey;
}
/**
* Finds a user by their API key.
* @param apiKey the API key
* @return the authenticated user or null if not found
*/
public AuthenticatedUser findUserByApiKey(String apiKey) {
UserAccountEntity userAccount = userAccountRepository.findByApiKey(apiKey);
if (userAccount == null) {
return null;
}
return buildAuthenticatedUser(userAccount);
}
private AuthenticatedUser buildAuthenticatedUser(UserAccountEntity userAccount) {
return switch (userAccount.role()) {
case student -> {
StudentEntity student = studentRepository.findById(userAccount.userId());
yield new AuthenticatedUser(userAccount.userId(), student.firstName(), student.lastName(), userAccount.emailAddress(), userAccount.passwordHash(), userAccount.role());
}
case instructor -> {
InstructorEntity instructor = instructorRepository.findById(userAccount.userId());
yield new AuthenticatedUser(userAccount.userId(), instructor.firstName(), instructor.lastName(), userAccount.emailAddress(), userAccount.passwordHash(), userAccount.role());
}
};
}
}