diff --git a/pom.xml b/pom.xml index f0a70ec..c229e61 100644 --- a/pom.xml +++ b/pom.xml @@ -17,19 +17,18 @@ 1.8 - - - - - - - - - + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + javax.xml.bind jaxb-api - 2.3.0 @@ -44,7 +43,6 @@ org.hibernate hibernate-entitymanager - 5.6.14.Final @@ -54,8 +52,6 @@ junit junit - 4.12 - test @@ -104,12 +100,10 @@ org.hibernate.validator hibernate-validator - 6.0.11.Final org.codehaus.groovy groovy - 2.4.15 test diff --git a/src/main/java/tacos/Ingredient.java b/src/main/java/tacos/Ingredient.java index dec1c32..1be7cd6 100644 --- a/src/main/java/tacos/Ingredient.java +++ b/src/main/java/tacos/Ingredient.java @@ -2,7 +2,6 @@ import javax.persistence.Entity; import javax.persistence.Id; -import javax.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.Data; @@ -10,13 +9,12 @@ import lombok.RequiredArgsConstructor; @Data -@Entity @RequiredArgsConstructor @NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) +@Entity public class Ingredient { @Id - @NotBlank private final String id; private final String name; private final Type type; diff --git a/src/main/java/tacos/Order.java b/src/main/java/tacos/Order.java index 1f56909..79def56 100644 --- a/src/main/java/tacos/Order.java +++ b/src/main/java/tacos/Order.java @@ -1,5 +1,6 @@ package tacos; +import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -9,20 +10,23 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; import javax.persistence.PrePersist; import javax.persistence.Table; import javax.validation.constraints.Digits; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.CreditCardNumber; +import org.hibernate.validator.constraints.NotBlank; import lombok.Data; @Data @Entity @Table(name = "Taco_Order") -public class Order { +public class Order implements Serializable { + + private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @@ -30,20 +34,23 @@ public class Order { private Date placedAt; - @NotBlank(message = "Name is required") - private String name; + @ManyToOne + private User user; + + @NotBlank(message = "Delivery name is required") + private String deliveryName; @NotBlank(message = "Street is required") - private String street; + private String deliveryStreet; @NotBlank(message = "City is required") - private String city; + private String deliveryCity; @NotBlank(message = "State is required") - private String state; + private String deliveryState; @NotBlank(message = "Zip code is required") - private String zip; + private String deliveryZip; @CreditCardNumber(message = "Not a valid credit card number") private String ccNumber; diff --git a/src/main/java/tacos/SpringInActionApplication.java b/src/main/java/tacos/SpringInActionApplication.java index 756becf..5c0939e 100644 --- a/src/main/java/tacos/SpringInActionApplication.java +++ b/src/main/java/tacos/SpringInActionApplication.java @@ -29,4 +29,4 @@ public CommandLineRunner dataLoader(IngredientRepository repo) { repo.save(new Ingredient("SRCR", "Sour Cream", Ingredient.Type.SAUCE)); }; } -} \ No newline at end of file +} diff --git a/src/main/java/tacos/Taco.java b/src/main/java/tacos/Taco.java index b74739b..b8afa35 100644 --- a/src/main/java/tacos/Taco.java +++ b/src/main/java/tacos/Taco.java @@ -1,6 +1,5 @@ package tacos; -import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -23,15 +22,15 @@ public class Taco { @GeneratedValue(strategy = GenerationType.AUTO) private Long id; - private Date createdAt; - @NotNull @Size(min = 5, message = "Name must be at least 5 characters long") private String name; + private Date createdAt; + @ManyToMany(targetEntity = Ingredient.class) @Size(min = 1, message = "You must choose at least 1 ingredient") - private List ingredients = new ArrayList<>(); + private List ingredients; @PrePersist void createdAt() { diff --git a/src/main/java/tacos/User.java b/src/main/java/tacos/User.java new file mode 100644 index 0000000..14dc2e1 --- /dev/null +++ b/src/main/java/tacos/User.java @@ -0,0 +1,65 @@ +package tacos; + +import java.util.Arrays; +import java.util.Collection; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +@Entity +@Data +@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) +@RequiredArgsConstructor +public class User implements UserDetails { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private final String username; + private final String password; + private final String fullname; + private final String street; + private final String city; + private final String state; + private final String zip; + private final String phoneNumber; + + @Override + public Collection getAuthorities() { + return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/tacos/data/UserRepository.java b/src/main/java/tacos/data/UserRepository.java new file mode 100644 index 0000000..dc43e8e --- /dev/null +++ b/src/main/java/tacos/data/UserRepository.java @@ -0,0 +1,10 @@ +package tacos.data; + +import org.springframework.data.repository.CrudRepository; + +import tacos.User; + +public interface UserRepository extends CrudRepository { + + User findByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/tacos/security/ErrorHandler.java b/src/main/java/tacos/security/ErrorHandler.java new file mode 100644 index 0000000..4cb1763 --- /dev/null +++ b/src/main/java/tacos/security/ErrorHandler.java @@ -0,0 +1,17 @@ +package tacos.security; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; + +@ControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(value = Exception.class) + public ModelAndView handleAllExceptions(Exception ex) { + ModelAndView model = new ModelAndView("error"); + model.addObject("errorMessage", "An error occurred: " + ex.getMessage()); + return model; + } +} + diff --git a/src/main/java/tacos/security/ErrorResponse.java b/src/main/java/tacos/security/ErrorResponse.java new file mode 100644 index 0000000..c28e779 --- /dev/null +++ b/src/main/java/tacos/security/ErrorResponse.java @@ -0,0 +1,15 @@ +package tacos.security; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ErrorResponse { + + private String errorCode; + private String errorMessage; +} + diff --git a/src/main/java/tacos/security/RegistrationController.java b/src/main/java/tacos/security/RegistrationController.java new file mode 100644 index 0000000..d4dedb4 --- /dev/null +++ b/src/main/java/tacos/security/RegistrationController.java @@ -0,0 +1,30 @@ +package tacos.security; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import lombok.RequiredArgsConstructor; +import tacos.data.UserRepository; + +@Controller +@RequestMapping("/register") +@RequiredArgsConstructor +public class RegistrationController { + + private final UserRepository userRepo; + private final PasswordEncoder passwordEncoder; + + @GetMapping + public String registerForm() { + return "registration"; + } + + @PostMapping + public String processRegistration(RegistrationForm form) { + userRepo.save(form.toUser(passwordEncoder)); + return "redirect:/login"; + } +} diff --git a/src/main/java/tacos/security/RegistrationForm.java b/src/main/java/tacos/security/RegistrationForm.java new file mode 100644 index 0000000..18b6707 --- /dev/null +++ b/src/main/java/tacos/security/RegistrationForm.java @@ -0,0 +1,23 @@ +package tacos.security; + +import org.springframework.security.crypto.password.PasswordEncoder; + +import lombok.Data; +import tacos.User; + +@Data +public class RegistrationForm { + + private String username; + private String password; + private String fullname; + private String street; + private String city; + private String state; + private String zip; + private String phone; + + public User toUser(PasswordEncoder passwordEncoder) { + return new User(username, passwordEncoder.encode(password), fullname, street, city, state, zip, phone); + } +} \ No newline at end of file diff --git a/src/main/java/tacos/security/SecurityConfig.java b/src/main/java/tacos/security/SecurityConfig.java new file mode 100644 index 0000000..674200c --- /dev/null +++ b/src/main/java/tacos/security/SecurityConfig.java @@ -0,0 +1,48 @@ +package tacos.security; + +import lombok.RequiredArgsConstructor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.StandardPasswordEncoder; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserDetailsService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/design", "/orders") + .access("hasRole('ROLE_USER')") + .antMatchers("/", "/**") + .access("permitAll") + .and() + .formLogin() + .loginPage("/login") + .and() + .logout() + .logoutSuccessUrl("/"); + } + + @Bean + public PasswordEncoder encoder() { + return new StandardPasswordEncoder("53cr3t"); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + + auth.userDetailsService(userDetailsService) + .passwordEncoder(encoder()); + } +} diff --git a/src/main/java/tacos/security/UserRepositoryUserDetailsService.java b/src/main/java/tacos/security/UserRepositoryUserDetailsService.java new file mode 100644 index 0000000..cd2f3fa --- /dev/null +++ b/src/main/java/tacos/security/UserRepositoryUserDetailsService.java @@ -0,0 +1,26 @@ +package tacos.security; + +import lombok.RequiredArgsConstructor; +import tacos.User; +import tacos.data.UserRepository; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserRepositoryUserDetailsService implements UserDetailsService { + + private final UserRepository userRepo; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepo.findByUsername(username); + if (user != null) { + return user; + } + throw new UsernameNotFoundException("User '" + username + "' not found"); + } +} diff --git a/src/main/java/tacos/web/DesignTacoController.java b/src/main/java/tacos/web/DesignTacoController.java index 8af4944..2b43937 100644 --- a/src/main/java/tacos/web/DesignTacoController.java +++ b/src/main/java/tacos/web/DesignTacoController.java @@ -1,5 +1,6 @@ package tacos.web; +import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -7,12 +8,15 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import tacos.Ingredient; import tacos.Ingredient.Type; import tacos.Order; import tacos.Taco; +import tacos.User; import tacos.data.IngredientRepository; import tacos.data.TacoRepository; +import tacos.data.UserRepository; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -24,15 +28,18 @@ import org.springframework.web.bind.annotation.SessionAttributes; @Controller -@RequiredArgsConstructor @RequestMapping("/design") @SessionAttributes("order") +@Slf4j +@RequiredArgsConstructor public class DesignTacoController { private final IngredientRepository ingredientRepo; private final TacoRepository tacoRepo; + private final UserRepository userRepo; + @ModelAttribute(name = "order") public Order order() { return new Order(); @@ -44,7 +51,8 @@ public Taco design() { } @GetMapping - public String showDesignForm(Model model) { + public String showDesignForm(Model model, Principal principal) { + log.info(" --- Designing taco"); List ingredients = new ArrayList<>(); ingredientRepo.findAll() .forEach(ingredients::add); @@ -55,12 +63,18 @@ public String showDesignForm(Model model) { .toLowerCase(), filterByType(ingredients, type)); } + String username = principal.getName(); + User user = userRepo.findByUsername(username); + model.addAttribute("user", user); + return "design"; } @PostMapping public String processDesign(@Valid Taco taco, Errors errors, @ModelAttribute Order order) { + log.info(" --- Saving taco"); + if (errors.hasErrors()) { return "design"; } diff --git a/src/main/java/tacos/web/OrderController.java b/src/main/java/tacos/web/OrderController.java index 367e1f7..9c2d602 100644 --- a/src/main/java/tacos/web/OrderController.java +++ b/src/main/java/tacos/web/OrderController.java @@ -2,9 +2,11 @@ import javax.validation.Valid; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.stereotype.Controller; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; @@ -12,6 +14,7 @@ import lombok.RequiredArgsConstructor; import tacos.Order; +import tacos.User; import tacos.data.OrderRepository; @Controller @@ -23,16 +26,37 @@ public class OrderController { private final OrderRepository orderRepo; @GetMapping("/current") - public String orderForm() { + public String orderForm(@AuthenticationPrincipal User user, @ModelAttribute Order order) { + if (order.getDeliveryName() == null) { + order.setDeliveryName(user.getFullname()); + } + if (order.getDeliveryStreet() == null) { + order.setDeliveryStreet(user.getStreet()); + } + if (order.getDeliveryCity() == null) { + order.setDeliveryCity(user.getCity()); + } + if (order.getDeliveryState() == null) { + order.setDeliveryState(user.getState()); + } + if (order.getDeliveryZip() == null) { + order.setDeliveryZip(user.getZip()); + } + return "orderForm"; } @PostMapping - public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus) { + public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, + @AuthenticationPrincipal User user) + { + if (errors.hasErrors()) { return "orderForm"; } + order.setUser(user); + orderRepo.save(order); sessionStatus.setComplete(); diff --git a/src/main/java/tacos/web/WebConfig.java b/src/main/java/tacos/web/WebConfig.java index fca7210..46c1e57 100644 --- a/src/main/java/tacos/web/WebConfig.java +++ b/src/main/java/tacos/web/WebConfig.java @@ -11,5 +11,6 @@ public class WebConfig implements WebMvcConfigurer { public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/") .setViewName("home"); + registry.addViewController("/login"); } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c9f5e13..6b046ce 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,5 @@ - spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/taco_db spring.datasource.username=${MYSQL_USER} spring.datasource.password=${MYSQL_PASS} -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver \ No newline at end of file +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/static/styles.css b/src/main/resources/static/styles.css index a2eaa4e..00fb984 100644 --- a/src/main/resources/static/styles.css +++ b/src/main/resources/static/styles.css @@ -1,15 +1,15 @@ div.ingredient-group:nth-child(odd) { - float: left; - padding-right: 20px; + float: left; + padding-right: 20px; } div.ingredient-group:nth-child(even) { - float: left; - padding-right: 0; + float: left; + padding-right: 0; } div.ingredient-group { - width: 50%; + width: 50%; } .grid:after { @@ -25,5 +25,5 @@ div.ingredient-group { } span.validationError { - color: red; -} \ No newline at end of file + color: red; +} diff --git a/src/main/resources/templates/design.html b/src/main/resources/templates/design.html index d54215e..1d6177c 100644 --- a/src/main/resources/templates/design.html +++ b/src/main/resources/templates/design.html @@ -10,7 +10,11 @@

Design your taco!

-
+ + +
+ +
+ + + Error + + +

Error

+

An error has occurred. Please try again later.

+ + diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 5ec3210..4265853 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -1,14 +1,17 @@ - - Taco Cloud - + + Taco Cloud + - -

Welcome to...

- + +

Welcome to...

+ + + + - Design a taco - +Design a taco + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..665553e --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,31 @@ + + + + Taco Cloud + + + +

Login

+ + +
+ Unable to login. Check your username and password. +
+ +

New here? Click + here to register.

+ + +
+ + +
+ + +
+ + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/orderForm.html b/src/main/resources/templates/orderForm.html index 4c034d2..b8b3715 100644 --- a/src/main/resources/templates/orderForm.html +++ b/src/main/resources/templates/orderForm.html @@ -1,85 +1,95 @@ - - Taco Cloud - - + + Taco Cloud + + - + -
-

Order your taco creations!

+ + +
- - Design another taco
+
+

Order your taco creations!

-
+ + +

Your tacos in this order:

+ Design another taco
+
    +
  • taco name
  • +
+ +
Please correct the problems below and resubmit. -
- -

Deliver my taco masterpieces to...

- - - Name Error -
- - - - Street Error -
- - - - City Error -
- - - - State Error -
- - - - Zip Error -
- -

Here's how I'll pay...

- - - CC Num Error -
- - - - CC Num Error -
- - - - CC Num Error -
- - - - - +
+ +

Deliver my taco masterpieces to...

+ + + Name Error +
+ + + + Street Error +
+ + + + City Error +
+ + + + State Error +
+ + + + Zip Error +
+ +

Here's how I'll pay...

+ + + CC Num Error +
+ + + + CC Num Error +
+ + + + CC Num Error +
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/registration.html b/src/main/resources/templates/registration.html new file mode 100644 index 0000000..00f385e --- /dev/null +++ b/src/main/resources/templates/registration.html @@ -0,0 +1,45 @@ + + + + Taco Cloud + + + +

Register

+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + \ No newline at end of file