Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/main/java/mate/academy/dao/UserDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.dao;

import mate.academy.model.User;

import java.util.Optional;

public interface UserDao {
User save(User user);

User get(Long id);

Optional<User> findByEmail(String email);
}
54 changes: 54 additions & 0 deletions src/main/java/mate/academy/dao/impl/UserDaoImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package mate.academy.dao.impl;

import mate.academy.dao.UserDao;
import java.util.Optional;
import mate.academy.model.User;
import mate.academy.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;

public class UserDaoImpl implements UserDao {
@Override
public User save(User user) {
Session session = null;
Transaction transaction = null;
try {
session = HibernateUtil.getSessionFactory().openSession();
transaction = session.beginTransaction();
session.save(user);
transaction.commit();
return user;
} catch (Exception exception) {
if (transaction != null) {
transaction.rollback();
}
throw new RuntimeException("Cannot save user", exception);
} finally {
if (session != null) {
session.close();
}
}
}

@Override
public User get(Long id) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
return session.get(User.class, id);
} catch (Exception exception) {
throw new RuntimeException("Cannot get user", exception);
}
}

@Override
public Optional<User> findByEmail(String email) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Query<User> query = session.createQuery("from User where email"
+ "=:email", User.class);
query.setParameter("email", email);
return query.uniqueResultOptional();
} catch (Exception exception) {
throw new RuntimeException("Cannot get user", exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.exception;

public class AuthenticationException extends Exception {
public AuthenticationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.exception;

public class RegistrationException extends Exception {
public RegistrationException(String message) {
super(message);
}
}
55 changes: 55 additions & 0 deletions src/main/java/mate/academy/model/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package mate.academy.model;

import jakarta.persistence.*;

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String email;

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."

private String password;
@Column(columnDefinition = "BLOB")
private byte[] salt;

public byte[] getSalt() {
return salt;
}

public void setSalt(byte[] salt) {
this.salt = salt;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{"
+ "id=" + id
+ ", email='" + email
+ '}';
}
}
11 changes: 11 additions & 0 deletions src/main/java/mate/academy/security/AuthenticationService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.security;

import mate.academy.exception.AuthenticationException;
import mate.academy.exception.RegistrationException;
import mate.academy.model.User;

public interface AuthenticationService {
User login(String email, String password) throws AuthenticationException;

User register(String email, String password) throws RegistrationException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package mate.academy.security.impl;

import mate.academy.exception.AuthenticationException;
import mate.academy.exception.RegistrationException;
import mate.academy.lib.Inject;
import mate.academy.model.User;
import mate.academy.security.AuthenticationService;
import mate.academy.service.UserService;
import mate.academy.util.HashUtil;
import java.util.Optional;

public class AuthenticationServiceImpl implements AuthenticationService {
@Inject
private UserService userService;

@Override
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())) {
throw new AuthenticationException("Invalid password or email");
}
return user;
}

@Override
public User register(String email, String password) throws RegistrationException {
if (!userService.findByEmail(email).isEmpty()){
throw new RegistrationException("During registration, check that the email is unique (i.e., not already used)");
}
User user = new User();
user.setEmail(email);
user.setPassword(password);

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.

return userService.add(user);
}
}
10 changes: 10 additions & 0 deletions src/main/java/mate/academy/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package mate.academy.service;

import mate.academy.model.User;
import java.util.Optional;

public interface UserService {
User add(User user);

Optional<User> findByEmail(String email);
}
25 changes: 25 additions & 0 deletions src/main/java/mate/academy/service/impl/UserServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mate.academy.service.impl;

import mate.academy.dao.UserDao;
import mate.academy.lib.Inject;
import java.util.Optional;
import mate.academy.model.User;
import mate.academy.service.UserService;
import mate.academy.util.HashUtil;

public class UserServiceImpl implements UserService {
@Inject
private UserDao userDao;

@Override
public User add(User user) {
user.setSalt(HashUtil.salt());
user.setPassword(HashUtil.hashPassword(user.getPassword(), user.getSalt()));
return userDao.save(user);
}

@Override
public Optional<User> findByEmail(String email) {
return userDao.findByEmail(email);
}
}
34 changes: 34 additions & 0 deletions src/main/java/mate/academy/util/HashUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package mate.academy.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class HashUtil {

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.

private HashUtil() {
}

private static final String ALGORITHM = "SHA-256";

public static byte[] salt() {
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[16];
secureRandom.nextBytes(salt);
return salt;
}

public static String hashPassword(String password, byte[] salt) {
StringBuilder stringBuilder = new StringBuilder();
try {
MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
messageDigest.update(salt);

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.

byte[] digest = messageDigest.digest(password.getBytes());
for (byte b : digest) {
stringBuilder.append(String.format("%02x",b));
}
return stringBuilder.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
21 changes: 21 additions & 0 deletions src/main/resources/hibernate.cfg.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</property>
<property name="connection.url">jdbc:mysql://localhost:3306/ticket_app</property>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.username">root</property>
<property name="connection.password">yourpassword</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">update</property>

<mapping class="mate.academy.model.CinemaHall"/>
<mapping class="mate.academy.model.Movie"/>
<mapping class="mate.academy.model.MovieSession"/>
<mapping class="mate.academy.model.User"/>
</session-factory>
</hibernate-configuration>
Loading