source: backend/src/main/java/mk/finki/roomreservation/service/AuthService.java

Last change on this file was 09e02d7, checked in by Nikola Sarafimov <sarafimov.nikola12345@…>, 4 days ago

Final room reservation system implementation

  • Property mode set to 100644
File size: 9.1 KB
RevLine 
[09e02d7]1package mk.finki.roomreservation.service;
2
3import mk.finki.roomreservation.model.ActivateAccountRequest;
4import mk.finki.roomreservation.model.LoginRequest;
5import mk.finki.roomreservation.model.RegisterRequest;
6import mk.finki.roomreservation.model.UserOption;
7import org.springframework.jdbc.core.JdbcTemplate;
8import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9import org.springframework.stereotype.Service;
10import org.springframework.transaction.annotation.Transactional;
11
12import java.util.List;
13import java.util.regex.Pattern;
14
15@Service
16public class AuthService {
17
18 private static final Pattern EMAIL_PATTERN =
19 Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
20
21 private final JdbcTemplate jdbcTemplate;
22 private final BCryptPasswordEncoder passwordEncoder;
23
24 public AuthService(JdbcTemplate jdbcTemplate, BCryptPasswordEncoder passwordEncoder) {
25 this.jdbcTemplate = jdbcTemplate;
26 this.passwordEncoder = passwordEncoder;
27 }
28
29 public UserOption login(LoginRequest request) {
30 validateLoginRequest(request);
31
32 UserOption user = findUserByIdentifier(request.identifier());
33 String passwordHash = findPasswordHash(user.userId());
34
35 if (!passwordEncoder.matches(request.password(), passwordHash)) {
36 throw new IllegalArgumentException("Invalid username/email or password.");
37 }
38
39 return user;
40 }
41
42 @Transactional
43 public UserOption register(RegisterRequest request) {
44 validateRegisterRequest(request);
45
46 String fullName = request.fullName().trim();
47 String username = request.username().trim();
48 String email = request.email().trim();
49
50 if (usernameExists(username)) {
51 throw new IllegalArgumentException("Username is already taken.");
52 }
53
54 if (emailExists(email)) {
55 throw new IllegalArgumentException("Email is already registered.");
56 }
57
58 Integer userId = jdbcTemplate.queryForObject(
59 """
60 INSERT INTO project.users (
61 username,
62 email,
63 full_name,
64 role
65 )
66 VALUES (?, ?, ?, 'regular')
67 RETURNING user_id
68 """,
69 Integer.class,
70 username,
71 email,
72 fullName
73 );
74
75 if (userId == null) {
76 throw new RuntimeException("User registration failed.");
77 }
78
79 String passwordHash = passwordEncoder.encode(request.password());
80
81 jdbcTemplate.update(
82 """
83 INSERT INTO project.user_credentials (
84 user_id,
85 password_hash
86 )
87 VALUES (?, ?)
88 """,
89 userId,
90 passwordHash
91 );
92
93 return findUserById(userId);
94 }
95
96 @Transactional
97 public UserOption activateExistingAccount(ActivateAccountRequest request) {
98 validateActivationRequest(request);
99
100 UserOption user = findUserByIdentifier(request.identifier());
101
102 if (credentialsExist(user.userId())) {
103 throw new IllegalArgumentException("This account already has a password.");
104 }
105
106 String passwordHash = passwordEncoder.encode(request.password());
107
108 jdbcTemplate.update(
109 """
110 INSERT INTO project.user_credentials (
111 user_id,
112 password_hash
113 )
114 VALUES (?, ?)
115 """,
116 user.userId(),
117 passwordHash
118 );
119
120 return user;
121 }
122
123 private void validateLoginRequest(LoginRequest request) {
124 if (request == null) {
125 throw new IllegalArgumentException("Login request is required.");
126 }
127
128 if (request.identifier() == null || request.identifier().isBlank()) {
129 throw new IllegalArgumentException("Username or email is required.");
130 }
131
132 if (request.password() == null || request.password().isBlank()) {
133 throw new IllegalArgumentException("Password is required.");
134 }
135 }
136
137 private void validateRegisterRequest(RegisterRequest request) {
138 if (request == null) {
139 throw new IllegalArgumentException("Register request is required.");
140 }
141
142 if (request.fullName() == null || request.fullName().isBlank()) {
143 throw new IllegalArgumentException("Full name is required.");
144 }
145
146 if (request.username() == null || request.username().isBlank()) {
147 throw new IllegalArgumentException("Username is required.");
148 }
149
150 if (request.email() == null || request.email().isBlank()) {
151 throw new IllegalArgumentException("Email is required.");
152 }
153
154 if (!EMAIL_PATTERN.matcher(request.email().trim()).matches()) {
155 throw new IllegalArgumentException("Email format is not valid.");
156 }
157
158 validatePassword(request.password());
159 }
160
161 private void validateActivationRequest(ActivateAccountRequest request) {
162 if (request == null) {
163 throw new IllegalArgumentException("Activation request is required.");
164 }
165
166 if (request.identifier() == null || request.identifier().isBlank()) {
167 throw new IllegalArgumentException("Username or email is required.");
168 }
169
170 validatePassword(request.password());
171 }
172
173 private void validatePassword(String password) {
174 if (password == null || password.isBlank()) {
175 throw new IllegalArgumentException("Password is required.");
176 }
177
178 if (password.length() < 8) {
179 throw new IllegalArgumentException("Password must contain at least 8 characters.");
180 }
181 }
182
183 private UserOption findUserByIdentifier(String identifier) {
184 List<UserOption> users = jdbcTemplate.query(
185 """
186 SELECT
187 user_id,
188 username,
189 email,
190 full_name,
191 role
192 FROM project.users
193 WHERE LOWER(username) = LOWER(?)
194 OR LOWER(email) = LOWER(?)
195 ORDER BY user_id
196 LIMIT 1
197 """,
198 (rs, rowNum) -> new UserOption(
199 rs.getInt("user_id"),
200 rs.getString("username"),
201 rs.getString("email"),
202 rs.getString("full_name"),
203 rs.getString("role")
204 ),
205 identifier.trim(),
206 identifier.trim()
207 );
208
209 if (users.isEmpty()) {
210 throw new IllegalArgumentException("Invalid username/email or password.");
211 }
212
213 return users.get(0);
214 }
215
216 private UserOption findUserById(int userId) {
217 return jdbcTemplate.queryForObject(
218 """
219 SELECT
220 user_id,
221 username,
222 email,
223 full_name,
224 role
225 FROM project.users
226 WHERE user_id = ?
227 """,
228 (rs, rowNum) -> new UserOption(
229 rs.getInt("user_id"),
230 rs.getString("username"),
231 rs.getString("email"),
232 rs.getString("full_name"),
233 rs.getString("role")
234 ),
235 userId
236 );
237 }
238
239 private String findPasswordHash(int userId) {
240 List<String> hashes = jdbcTemplate.query(
241 """
242 SELECT password_hash
243 FROM project.user_credentials
244 WHERE user_id = ?
245 """,
246 (rs, rowNum) -> rs.getString("password_hash"),
247 userId
248 );
249
250 if (hashes.isEmpty()) {
251 throw new IllegalArgumentException("This account is not activated yet. Please request access.");
252 }
253
254 return hashes.get(0);
255 }
256
257 private boolean usernameExists(String username) {
258 Long count = jdbcTemplate.queryForObject(
259 """
260 SELECT COUNT(*)
261 FROM project.users
262 WHERE LOWER(username) = LOWER(?)
263 """,
264 Long.class,
265 username
266 );
267
268 return count != null && count > 0;
269 }
270
271 private boolean emailExists(String email) {
272 Long count = jdbcTemplate.queryForObject(
273 """
274 SELECT COUNT(*)
275 FROM project.users
276 WHERE LOWER(email) = LOWER(?)
277 """,
278 Long.class,
279 email
280 );
281
282 return count != null && count > 0;
283 }
284
285 private boolean credentialsExist(int userId) {
286 Long count = jdbcTemplate.queryForObject(
287 """
288 SELECT COUNT(*)
289 FROM project.user_credentials
290 WHERE user_id = ?
291 """,
292 Long.class,
293 userId
294 );
295
296 return count != null && count > 0;
297 }
298}
Note: See TracBrowser for help on using the repository browser.