-
Notifications
You must be signed in to change notification settings - Fork 905
I almost have completed #963
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Summary
REQUEST_CHANGES
Thank you for your hard work on this assignment! There are several critical issues that must be addressed before your submission can be approved, as they block core functionality and violate explicit checklist requirements:
1. Incorrect DAO Method Signature and Logic
- The
UserDao
andUserDaoImpl
usegetByUsername(String username)
instead of the requiredfindByEmail(String email)
. This breaks user lookup by email, which is essential for registration and login. - The implementation retrieves by primary key, not by email. You must use a query to find by the
email
field and return anOptional<User>
usinguniqueResultOptional()
. - All DAO methods must have proper exception handling (try-catch-finally with transaction rollback where needed).
2. Exception Handling and Declaration
AuthenticationException
extendsRuntimeException
, but it must extendException
(checked exception).- The
AuthenticationService
interface and its implementation must declarethrows AuthenticationException
forlogin
andthrows RegistrationException
forregister
. - In
AuthenticationServiceImpl.login()
, combine the user existence and password checks so that only oneAuthenticationException
is thrown if either fails.
3. Registration Logic
- In
AuthenticationServiceImpl.register()
, you must check if the email is already used (viafindByEmail
) and throwRegistrationException
if so. This prevents duplicate registrations.
4. Sensitive Data in User.toString()
- The
User
class'stoString()
method currently includes thepassword
field. For security, removepassword
(andsalt
, if present) from the output.
5. Utility Class Best Practices
- The
HashUtil
class must have a private constructor to prevent instantiation.
6. Minor but Important
- Consider adding a uniqueness constraint to the
email
field in theUser
entity (e.g.,@Column(unique = true)
). - Fix the typo in
HashUtil
(ALGORITM
→ALGORITHM
).
Next Steps:
- Update all method names and signatures as required.
- Implement the correct logic for finding users by email.
- Ensure all exceptions are handled and declared properly.
- Remove sensitive information from
toString()
. - Add a private constructor to
HashUtil
.
Once these changes are made, your code will be much closer to meeting all requirements. You're on the right track—keep going! If you have any questions about the requirements or how to implement these changes, feel free to ask.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review
button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor
tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
public interface UserDao { | ||
User save(User user); | ||
User get(Long id); | ||
Optional<User> getByUsername(String username); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #2: 'Implement a UserDao with at least: ... A method Optional findByEmail(String email) that returns an Optional.'
The method here is named getByUsername
and uses username
instead of findByEmail
and email
. You must rename this method to findByEmail(String email)
and ensure it matches the required signature.
} | ||
|
||
@Override | ||
public Optional<User> getByUsername(String username) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #2: 'Implement a UserDao with at least: ... A method Optional findByEmail(String email) that returns an Optional.'
The method here is named getByUsername
and uses username
instead of findByEmail
and email
. You must rename this method to findByEmail(String email)
and ensure it matches the required signature.
@Override | ||
public Optional<User> getByUsername(String username) { | ||
Session session = HibernateUtil.getSessionFactory().openSession(); | ||
return Optional.of(session.get(User.class, username)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation here is incorrect. session.get(User.class, username)
retrieves by primary key, not by email. You need to use a query to find a user by their email field, and use uniqueResultOptional()
as suggested in the requirements.
public User get(Long id) { | ||
Session session = HibernateUtil.getSessionFactory().openSession(); | ||
return session.get(User.class, id); | ||
} | ||
|
||
@Override | ||
public Optional<User> getByUsername(String username) { | ||
Session session = HibernateUtil.getSessionFactory().openSession(); | ||
return Optional.of(session.get(User.class, username)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #5: 'All DAO operations must have catch blocks for all types of operations.'
The get
and getByUsername
methods do not have exception handling or transaction management. You should add try-catch-finally blocks similar to the save
method.
@@ -0,0 +1,7 @@ | |||
package mate.academy.exception; | |||
|
|||
public class AuthenticationException extends RuntimeException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #3: 'AuthenticationException and RegistrationException must NOT extend RuntimeException (they must be checked exceptions).'
You should extend Exception
instead of RuntimeException
.
if (byEmail.isEmpty()) { | ||
throw new AuthenticationException("Invalid email"); | ||
} | ||
User user = byEmail.get(); | ||
String hashedPassword = HashUtil.hashPassword(password, user.getSalt()); | ||
if (!hashedPassword.equals(user.getPassword())) { | ||
throw new AuthenticationException("Invalid password"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #9: 'In login() method, only one condition should throw AuthenticationException (combine user existence and password match checks).'
You throw AuthenticationException
separately for invalid email and invalid password. You should combine these checks and throw a single exception if either condition fails.
} | ||
|
||
@Override | ||
public User register(String email, String password) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #4: 'Implement an AuthenticationService interface with: User login(String email, String password) that throws AuthenticationException; User register(String email, String password) that throws RegistrationException.'
The register
method does not declare throws RegistrationException
in its signature.
public User register(String email, String password) { | ||
User user = new User(); | ||
user.setEmail(email); | ||
user.setPassword(password); | ||
return userService.add(user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #2: 'During registration, check that the email is unique (i.e., not already used).' and checklist item #4: 'User register(String email, String password) that throws RegistrationException.'
You must check if the email is already used and throw RegistrationException
if so.
|
||
@Override | ||
public Optional<User> findByEmail(String email) { | ||
return userDao.getByUsername(email); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #2: 'Implement a UserDao with at least: ... A method Optional findByEmail(String email) that returns an Optional.'
You are calling userDao.getByUsername(email)
, but the required method is findByEmail(String email)
. Update this call once the DAO is corrected.
import java.security.NoSuchAlgorithmException; | ||
import java.security.SecureRandom; | ||
|
||
public class HashUtil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This violates checklist item #4: 'Add a private constructor to HashUtil class.'
You must add a private constructor to prevent instantiation of this utility class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Summary
REQUEST_CHANGES
Thank you for your hard work on this assignment! You have a solid foundation, but there are several critical issues that must be fixed before your code can be approved. These issues directly impact core functionality and violate explicit checklist requirements:
1. Incorrect findByEmail
Implementation in DAO
- In
UserDaoImpl
,findByEmail
usessession.get(User.class, email)
, which looks up by primary key, not by email. You must use a query to search by theemail
field and retrieve the result withuniqueResultOptional()
. Example:session.createQuery("FROM User u WHERE u.email = :email", User.class) .setParameter("email", email) .uniqueResultOptional();
2. Missing Exception Handling in DAO Methods
- All DAO methods (including
findByEmail
andget
) must have try-catch blocks to handle exceptions and roll back transactions if needed. This is required for robust transaction management.
3. Email Uniqueness Not Enforced
- The
email
field in theUser
entity must be annotated with a uniqueness constraint, e.g.,@Column(unique = true)
, to ensure the database enforces unique emails. - During registration (in
AuthenticationServiceImpl
), you must check if the email is already used (viauserService.findByEmail
) and throw aRegistrationException
if it is.
4. Incorrect Exception Signatures
- The
register
method in bothAuthenticationService
andAuthenticationServiceImpl
must declarethrows RegistrationException
.
5. Login Exception Logic
- In
AuthenticationServiceImpl.login
, you currently check for user existence and password match separately, throwing exceptions in both cases. You must combine these checks and throw a singleAuthenticationException
if either the user does not exist or the password does not match, as per the checklist.
6. Service Layer Calls Wrong DAO Method
- In
UserServiceImpl
,findByEmail
should calluserDao.findByEmail(email)
, notgetByUsername(email)
.
7. HashUtil Issues
- There is a typo in the constant name:
ALGORITM
should beALGORITHM
. - The password hashing logic does not use the salt correctly. You should call
messageDigest.update(salt)
before digesting the password bytes, so the salt is included in the hash computation.
Next Steps:
- Fix the above issues, focusing on correct DAO queries, exception handling, enforcing email uniqueness, and proper password hashing.
- Double-check all method signatures against the checklist.
You're very close! Once these blockers are resolved, your code will be robust and ready for approval. Keep up the good work and let me know if you have any questions about these changes.
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Important
Fix the issues, commit the changes, and then re-request my review using the Re-request review
button2 in the Reviewers tab.
Tip
If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor
tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.
Caution
Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.
Footnotes
@Override | ||
public Optional<User> findByEmail(String email) { | ||
try (Session session = HibernateUtil.getSessionFactory().openSession()) { | ||
return Optional.of(session.get(User.class, email)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The findByEmail
method incorrectly uses session.get(User.class, email)
, which retrieves by primary key, not by the email
field. You must use a query to find by the email
field and use uniqueResultOptional()
as required by the checklist: "In findByEmail() DAO method, use uniqueResultOptional() to retrieve the user."
public Optional<User> findByEmail(String email) { | ||
try (Session session = HibernateUtil.getSessionFactory().openSession()) { | ||
return Optional.of(session.get(User.class, email)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The findByEmail
method lacks exception handling. All DAO operations must have catch blocks for exceptions, as required by the checklist: "Always add catch blocks for all DAO operations."
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
private String email; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The email
field should be annotated with a uniqueness constraint (e.g., @jakarta.persistence.Column(unique = true)
) to ensure email uniqueness at the database level, as required by the checklist: "The User email must be unique in the database."
public interface AuthenticationService { | ||
User login(String email, String password) throws AuthenticationException; | ||
|
||
User register(String email, String password); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The register
method must declare throws RegistrationException
in its signature, as required by the checklist: "AuthenticationService interface with methods: User login(String email, String password) throws AuthenticationException; User register(String email, String password) throws RegistrationException;"
} | ||
|
||
@Override | ||
public User register(String email, String password) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The register
method must declare throws RegistrationException
in its signature, as required by the checklist: "AuthenticationService interface with methods: User login(String email, String password) throws AuthenticationException; User register(String email, String password) throws RegistrationException;"
public User register(String email, String password) { | ||
User user = new User(); | ||
user.setEmail(email); | ||
user.setPassword(password); | ||
return userService.add(user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The register
method must check if the email is already used (via userService.findByEmail
) and throw RegistrationException
if so, as required by the checklist: "During registration, check that the email is unique (i.e., not already used)."
public User login(String email, String password) throws AuthenticationException { | ||
Optional<User> byEmail = userService.findByEmail(email); | ||
if (byEmail.isEmpty()) { | ||
throw new AuthenticationException("Invalid email"); | ||
} | ||
User user = byEmail.get(); | ||
String hashedPassword = HashUtil.hashPassword(password, user.getSalt()); | ||
if (!hashedPassword.equals(user.getPassword()) || byEmail.isEmpty()) { | ||
throw new AuthenticationException("Invalid password or email"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the login
method, you check for byEmail.isEmpty()
twice (once before password check, and again in the password check condition). The checklist requires that only one AuthenticationException
is thrown if either the user does not exist or the password does not match, and the check should be combined.
|
||
@Override | ||
public Optional<User> findByEmail(String email) { | ||
return userDao.getByUsername(email); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The findByEmail
method incorrectly calls userDao.getByUsername(email)
. It must call userDao.findByEmail(email)
to match the interface and requirements: "findByEmail() must accept only a single String parameter (the email)" and "findByEmail() must return Optional on both DAO and Service layers."
private HashUtil() { | ||
} | ||
|
||
private static final String ALGORITM = "SHA-256"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a typo in the constant name: ALGORITM
should be ALGORITHM
. This could cause a runtime error if the algorithm name is used as-is.
try { | ||
MessageDigest messageDigest = MessageDigest.getInstance(ALGORITM); | ||
byte[] digest = messageDigest.digest(password.getBytes()); | ||
messageDigest.update(salt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The password hashing logic is incorrect: messageDigest.update(salt)
should be called before digest(password.getBytes())
, or the salt should be included in the digest computation. As written, the salt is not actually used in the hash computation. This violates the requirement to use salt and password hashing for user passwords.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review Summary
This is an educational approval to help you continue progressing in your learning journey!
What you did well:
- You implemented the core structure of the application, including interfaces, service, and DAO layers.
- Secure password handling with hashing and salt is correctly set up in your service layer.
- Exception handling and custom checked exceptions are used as required.
- You enforced email uniqueness both in the database and in your registration logic.
Areas for improvement:
- In
UserDaoImpl.java
, thefindByEmail
method should use a query to search by theemail
field, notsession.get(User.class, email)
. This ensures users are found by their email, not just by primary key.
Hint: Use a query like:session.createQuery("FROM User u WHERE u.email = :email", User.class) .setParameter("email", email) .uniqueResultOptional();
- In
AuthenticationServiceImpl.java
, thelogin
method should combine the user existence and password check into a single condition that throwsAuthenticationException
. This makes your code cleaner and matches the requirements. - Double-check that password hashing and salt generation are only handled in
UserService.add()
, not directly in the registration method.
Keep going!
You’re making great progress and showing a solid understanding of Java, Hibernate, and secure authentication practices. Review the comments for specific improvements, and keep up the good work—each iteration brings you closer to mastery!
✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨
Footnotes
@Override | ||
public Optional<User> findByEmail(String email) { | ||
try (Session session = HibernateUtil.getSessionFactory().openSession()) { | ||
return Optional.ofNullable(session.get(User.class, email)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of findByEmail
is incorrect. It uses session.get(User.class, email)
, which looks up by primary key, not by the email
field. According to the requirements, you must use a query to search by the email
field and retrieve the result with uniqueResultOptional()
. Example:
return session.createQuery("FROM User u WHERE u.email = :email", User.class)
.setParameter("email", email)
.uniqueResultOptional();
This violates checklist item: 'In UserDaoImpl, findByEmail uses session.get(User.class, email), which looks up by primary key, not by email. You must use a query to search by the email field and retrieve the result with uniqueResultOptional().'
if (!hashedPassword.equals(user.getPassword()) || byEmail.isEmpty()) { | ||
throw new AuthenticationException("Invalid password or email"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The login
method currently throws an AuthenticationException
for user not found and then again for password mismatch. The checklist requires that only one condition should throw AuthenticationException
(combine user existence and password match checks). Also, the check byEmail.isEmpty()
in the second condition is redundant since it's already checked above. Refactor to combine these checks into a single condition as required.
} | ||
User user = new User(); | ||
user.setEmail(email); | ||
user.setPassword(password); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the register
method, you set the password directly on the user object. According to the requirements, password hashing and salt generation must be performed securely in UserService.add()
. Please ensure that UserService.add()
handles this. If not, update the logic accordingly.
No description provided.