-
Notifications
You must be signed in to change notification settings - Fork 0
Implement login with DTO and secure password validation #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a9fc5ef
f4cdb19
35b2d10
3e31486
13745ee
c139707
14a0cb2
425dbf6
bdcbf05
6e95838
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package org.example.alfs.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
|
||
| @Configuration | ||
| public class PasswordConfig { | ||
|
|
||
| // Makes password encoder available in the whole app | ||
| @Bean | ||
| public PasswordEncoder passwordEncoder(){ | ||
| return new BCryptPasswordEncoder(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package org.example.alfs.controllers; | ||
|
|
||
| import org.example.alfs.dto.auth.LoginRequestDTO; | ||
| import org.example.alfs.dto.auth.LoginResponseDTO; | ||
| import org.example.alfs.entities.User; | ||
| import org.example.alfs.services.AuthService; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import jakarta.validation.Valid; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/auth") | ||
| public class AuthController { | ||
|
|
||
| private final AuthService authService; | ||
|
|
||
| public AuthController(AuthService authService) { | ||
| this.authService = authService; | ||
| } | ||
|
|
||
| /** | ||
| * Handles user login by validating credentials and returning user details. | ||
| */ | ||
| @PostMapping("/login") | ||
| public LoginResponseDTO login(@Valid @RequestBody LoginRequestDTO request) { | ||
|
|
||
| User user = authService.login( | ||
| request.getUsername(), | ||
| request.getPassword() | ||
| ); | ||
|
|
||
| return new LoginResponseDTO( | ||
| user.getUsername(), | ||
| user.getRole().name() | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package org.example.alfs.dto.auth; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| public class LoginRequestDTO { | ||
|
|
||
| @NotBlank(message = "Username is required") | ||
| private String username; | ||
|
|
||
| @NotBlank(message = "Password is required") | ||
| private String password; | ||
|
simonforsberg marked this conversation as resolved.
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package org.example.alfs.dto.auth; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| public class LoginResponseDTO { | ||
|
|
||
| private String username; | ||
| private String role; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,14 @@ public class User { | |
| private String passwordHash; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(nullable = false) | ||
| private Role role; | ||
|
Comment on lines
+30
to
31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure existing users have roles before applying this constraint. Adding Consider adding a data migration (e.g., Flyway/Liquibase script or a SQL update) to set a default role for any existing null values before this constraint is enforced: UPDATE users SET role = 'REPORTER' WHERE role IS NULL;🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We dont have any existing database or persisted users yet, so this wont cause issues in our current setup. We'll handle migrations properly when we introduce a real database. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Just a reminder for when you introduce a real database: before applying this schema constraint in a migration script, make sure to run a backfill like the one suggested (e.g., |
||
|
|
||
| @PrePersist | ||
| public void prePersist() { | ||
| if (role == null) { | ||
| role = Role.REPORTER; | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package org.example.alfs.services; | ||
|
|
||
| import org.example.alfs.entities.User; | ||
| import org.example.alfs.repositories.UserRepository; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| @Service | ||
| public class AuthService { | ||
|
|
||
| private final PasswordEncoder passwordEncoder; | ||
| private final UserRepository userRepository; | ||
|
|
||
| public AuthService(PasswordEncoder passwordEncoder, UserRepository userRepository) { | ||
| this.passwordEncoder = passwordEncoder; | ||
| this.userRepository = userRepository; | ||
| } | ||
|
|
||
| /** | ||
| * Authenticates a user by verifying username and password. | ||
| */ | ||
| public User login(String username, String password) { | ||
|
|
||
| User user = userRepository.findByUsername(username) | ||
| .orElseThrow(() -> new ResponseStatusException( | ||
| HttpStatus.UNAUTHORIZED, | ||
| "Invalid username or password" | ||
| )); | ||
| if (!passwordEncoder.matches(password, user.getPasswordHash())) { | ||
| throw new ResponseStatusException( | ||
| HttpStatus.UNAUTHORIZED, | ||
| "Invalid username or password" | ||
| ); | ||
| } | ||
|
|
||
| return user; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.