diff --git a/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml b/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml new file mode 100644 index 000000000..f25b99b8f --- /dev/null +++ b/.idea/libraries/Maven__io_jsonwebtoken_jjwt_0_9_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_security_2_5_4.xml b/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_security_2_5_4.xml new file mode 100644 index 000000000..68781cd55 --- /dev/null +++ b/.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_security_2_5_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_springframework_security_spring_security_config_5_5_2.xml b/.idea/libraries/Maven__org_springframework_security_spring_security_config_5_5_2.xml new file mode 100644 index 000000000..2c8cd9a55 --- /dev/null +++ b/.idea/libraries/Maven__org_springframework_security_spring_security_config_5_5_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_springframework_security_spring_security_web_5_5_2.xml b/.idea/libraries/Maven__org_springframework_security_spring_security_web_5_5_2.xml new file mode 100644 index 000000000..3562f1210 --- /dev/null +++ b/.idea/libraries/Maven__org_springframework_security_spring_security_web_5_5_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 797acea53..000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/demo.iml b/demo.iml index 173b7c31f..50784e268 100644 --- a/demo.iml +++ b/demo.iml @@ -34,7 +34,9 @@ + + @@ -56,6 +58,7 @@ + @@ -117,12 +120,13 @@ + + + + - - - - + diff --git a/pom.xml b/pom.xml index bb3ff4ffd..1702b33ee 100644 --- a/pom.xml +++ b/pom.xml @@ -46,11 +46,16 @@ test - + - org.springframework.security - spring-security-core - 5.5.2 + io.jsonwebtoken + jjwt + 0.9.1 + + + + org.springframework.boot + spring-boot-starter-security diff --git a/src/main/java/com/example/demo/config/ProfileConfig.java b/src/main/java/com/example/demo/config/ProfileConfig.java index 8305e7783..1b8d5689b 100644 --- a/src/main/java/com/example/demo/config/ProfileConfig.java +++ b/src/main/java/com/example/demo/config/ProfileConfig.java @@ -7,9 +7,4 @@ @Configuration public class ProfileConfig { - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } } diff --git a/src/main/java/com/example/demo/controller/ProfileController.java b/src/main/java/com/example/demo/controller/ProfileController.java index b84624126..f0046619a 100644 --- a/src/main/java/com/example/demo/controller/ProfileController.java +++ b/src/main/java/com/example/demo/controller/ProfileController.java @@ -1,10 +1,18 @@ package com.example.demo.controller; import com.example.demo.models.Profile; +import com.example.demo.security.JwtGenerator; +import com.example.demo.security.LoginRequest; +import com.example.demo.security.LoginResponse; import com.example.demo.service.ProfileService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -13,11 +21,27 @@ @RequestMapping(value = "/profile") public class ProfileController { + @Autowired + AuthenticationManager authenticationManager; + + @Autowired + PasswordEncoder passwordEncoder; + + @Autowired + JwtGenerator jwtGenerator; + @Autowired ProfileService service; @PostMapping(value = "/register") - public ResponseEntity createProfile(@RequestBody Profile profile) { + public ResponseEntity createProfile(@RequestBody Profile profile) { + if (service.existsByUsername(profile.getUsername())) { + return ResponseEntity.badRequest().body("Username is taken"); + } + if (service.existsByEmail(profile.getEmail())) { + return ResponseEntity.badRequest().body("Email is taken"); + } + profile.setPassword(passwordEncoder.encode(profile.getPassword())); return new ResponseEntity<>(service.createProfile(profile), HttpStatus.CREATED); } @@ -31,9 +55,19 @@ public ResponseEntity> findAllProfiles() { return new ResponseEntity<>(service.findAllProfiles(), HttpStatus.OK); } - @GetMapping(value = "/login/{username}/{password}") - public ResponseEntity login(@PathVariable String username, @PathVariable String password) { - return new ResponseEntity<>(service.login(username, password), HttpStatus.OK); + @PostMapping(value = "/login/{username}/{password}") + public ResponseEntity login(@PathVariable String username, @PathVariable String password) { + Authentication authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken( + username, password)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + String token = jwtGenerator.generateToken(username); + Profile profile = service.findByUsername(username); + return new ResponseEntity<>(new LoginResponse(profile.getId(), + token, profile.getFirstName(), profile.getLastName(), + profile.getUsername(), profile.getEmail() + ), HttpStatus.OK); } @PutMapping(value = "/update") diff --git a/src/main/java/com/example/demo/models/Channel.java b/src/main/java/com/example/demo/models/Channel.java index 2d6a08f2f..2ebe3e9e5 100644 --- a/src/main/java/com/example/demo/models/Channel.java +++ b/src/main/java/com/example/demo/models/Channel.java @@ -1,5 +1,8 @@ package com.example.demo.models; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + import javax.persistence.*; import java.util.List; @@ -11,11 +14,24 @@ public class Channel { @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; - @ManyToMany(mappedBy = "channels") + @Enumerated(value = EnumType.STRING) + private ChannelType type; + @ManyToMany(fetch = FetchType.LAZY) private List profileList; - @OneToMany(mappedBy = "channel") + @OneToMany(fetch = FetchType.LAZY) private List messages; + public Channel() { + } + + public Channel(Long id, String name, ChannelType type, List profileList, List messages) { + this.id = id; + this.name = name; + this.type = type; + this.profileList = profileList; + this.messages = messages; + } + public Long getId() { return id; } @@ -47,4 +63,12 @@ public List getMessages() { public void setMessages(List messages) { this.messages = messages; } + + public ChannelType getType() { + return type; + } + + public void setType(ChannelType type) { + this.type = type; + } } diff --git a/src/main/java/com/example/demo/models/ChannelType.java b/src/main/java/com/example/demo/models/ChannelType.java new file mode 100644 index 000000000..f0d76de03 --- /dev/null +++ b/src/main/java/com/example/demo/models/ChannelType.java @@ -0,0 +1,6 @@ +package com.example.demo.models; + +public enum ChannelType { + DM, + CHANNEL; +} diff --git a/src/main/java/com/example/demo/models/Message.java b/src/main/java/com/example/demo/models/Message.java index b422f4078..7105aa569 100644 --- a/src/main/java/com/example/demo/models/Message.java +++ b/src/main/java/com/example/demo/models/Message.java @@ -13,26 +13,25 @@ public class Message { @Id @GeneratedValue(strategy = GenerationType.AUTO) Long id; - @ManyToOne - @JoinColumn(name = "profile_id") - // @JsonIgnoreProperties("messages") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", referencedColumnName = "id") Profile profile; String body; String timestamp; - @ManyToOne - @JoinColumn(name = "channel_id") - // @JsonIgnoreProperties("messages") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "channel_id", referencedColumnName = "id") Channel channel; public Message() { } - public Message(Long id, Profile profile, String body, String timestamp) { + public Message(Long id, Profile profile, String body, String timestamp, Channel channel) { this.id = id; this.profile = profile; this.body = body; this.timestamp = timestamp; + this.channel = channel; } public Long getId() { @@ -43,14 +42,6 @@ public void setId(Long id) { this.id = id; } - public Profile getProfile() { - return profile; - } - - public void setProfile(Profile profile) { - this.profile = profile; - } - public String getBody() { return body; } @@ -67,4 +58,19 @@ public void setTimestamp(String timestamp) { this.timestamp = timestamp; } + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + + public Channel getChannel() { + return channel; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } } diff --git a/src/main/java/com/example/demo/models/Profile.java b/src/main/java/com/example/demo/models/Profile.java index 28f3fdb20..c0c41f6fd 100644 --- a/src/main/java/com/example/demo/models/Profile.java +++ b/src/main/java/com/example/demo/models/Profile.java @@ -2,48 +2,111 @@ import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonManagedReference; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; +import java.util.Collection; import java.util.List; @Entity -public class Profile { +public class Profile implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; + private String token; private String firstName; private String lastName; private String username; private String password; private String email; - @ManyToMany - @JoinColumn(name = "channel_id", referencedColumnName = "id") - private List channels; - @OneToMany(mappedBy = "profile") - List messages; + private boolean enabled = true; +// @ManyToMany(cascade = CascadeType.ALL) +// @JoinTable(joinColumns = @JoinColumn(name = "profile_id"), +// inverseJoinColumns = @JoinColumn(name = "channel_id")) +// private List channels; +// @OneToMany(mappedBy = "profile") +// private List messages; public Profile() { } - public Profile(Long id, String firstName, String lastName, String username, String password, String email, List channels, List messages) { + public Profile(String username, String password) { + this.username = username; + this.password = password; + } + + public Profile(Long id, String token, String firstName, String lastName, String username, String password, String email, boolean enabled) { this.id = id; + this.token = token; this.firstName = firstName; this.lastName = lastName; this.username = username; this.password = password; this.email = email; - this.channels = channels; - this.messages = messages; + this.enabled = enabled; + } + // public Profile(Long id, String token, String firstName, String lastName, String username, String password, String email, boolean enabled, List channels) { +// this.id = id; +// this.token = token; +// this.firstName = firstName; +// this.lastName = lastName; +// this.username = username; +// this.password = password; +// this.email = email; +// this.enabled = enabled; +// this.channels = channels; +// } +// +// public Profile(Long id, String token, String firstName, String lastName, String username, String password, String email, boolean enabled, List channels, List messages) { +// this.id = id; +// this.token = token; +// this.firstName = firstName; +// this.lastName = lastName; +// this.username = username; +// this.password = password; +// this.email = email; +// this.enabled = enabled; +// this.channels = channels; +// this.messages = messages; +// } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return enabled; + } + + @Override + public boolean isAccountNonLocked() { + return enabled; + } + + @Override + public boolean isCredentialsNonExpired() { + return enabled; + } + + @Override + public boolean isEnabled() { + return enabled; } - public List getMessages() { - return messages; + @Override + public Collection getAuthorities() { + return null; } - public void setMessages(List messages) { - this.messages = messages; + @Override + public String getPassword() { + return password; } public Long getId() { @@ -70,18 +133,10 @@ public void setLastName(String lastName) { this.lastName = lastName; } - public String getUsername() { - return username; - } - public void setUsername(String username) { this.username = username; } - public String getPassword() { - return password; - } - public void setPassword(String password) { this.password = password; } @@ -94,11 +149,31 @@ public void setEmail(String email) { this.email = email; } - public List getChannels() { - return channels; + public void setEnabled(boolean enabled) { + this.enabled = enabled; } - public void setChannels(List channels) { - this.channels = channels; +// public List getChannels() { +// return channels; +// } +// +// public void setChannels(List channels) { +// this.channels = channels; +// } + + public String getToken() { + return token; } + + public void setToken(String token) { + this.token = token; + } + +// public List getMessages() { +// return messages; +// } +// +// public void setMessages(List messages) { +// this.messages = messages; +// } } diff --git a/src/main/java/com/example/demo/repository/ProfileRepo.java b/src/main/java/com/example/demo/repository/ProfileRepo.java index 7b4b5d8b3..3ca7101a5 100644 --- a/src/main/java/com/example/demo/repository/ProfileRepo.java +++ b/src/main/java/com/example/demo/repository/ProfileRepo.java @@ -6,7 +6,11 @@ @Repository public interface ProfileRepo extends JpaRepository { - Profile findByUsernameAndPassword(String username, String password); +// Profile findByUsernameAndPassword(String username, String password); Profile findByUsername(String username); + + boolean existsByUsername(String username); + + boolean existsByEmail(String email); } diff --git a/src/main/java/com/example/demo/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/example/demo/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 000000000..b3cfd6105 --- /dev/null +++ b/src/main/java/com/example/demo/security/JwtAuthenticationEntryPoint.java @@ -0,0 +1,19 @@ +package com.example.demo.security; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { + httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); + } +} diff --git a/src/main/java/com/example/demo/security/JwtFilter.java b/src/main/java/com/example/demo/security/JwtFilter.java new file mode 100644 index 000000000..0a812d70b --- /dev/null +++ b/src/main/java/com/example/demo/security/JwtFilter.java @@ -0,0 +1,41 @@ +package com.example.demo.security; + +import com.example.demo.models.Profile; +import com.example.demo.service.ProfileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class JwtFilter extends OncePerRequestFilter { + + @Autowired + private ProfileService service; + + @Autowired + private JwtGenerator jwtGenerator; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { + String token = jwtGenerator.getToken(httpServletRequest); + if (token != null) { + String username = jwtGenerator.getUsernameFromToken(token); + Profile profile = service.findByUsername(username); + if (jwtGenerator.validateToken(token)) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(profile, null, null); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } +} diff --git a/src/main/java/com/example/demo/security/JwtGenerator.java b/src/main/java/com/example/demo/security/JwtGenerator.java new file mode 100644 index 000000000..c397dfba2 --- /dev/null +++ b/src/main/java/com/example/demo/security/JwtGenerator.java @@ -0,0 +1,66 @@ +package com.example.demo.security; + +import com.example.demo.models.Profile; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +@Component +public class JwtGenerator { + + @Value("${chatter-box.app.jwtSecret}") + private String secret; + @Value("${chatter-box.app.jwtExpirationMs}") + private int expiration; + + private Claims getTokenBody(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + public String getUsernameFromToken(String token) { + return getTokenBody(token) + .getSubject(); + } + + public Date getExpirationDate(String token) { + return getTokenBody(token) + .getExpiration(); + } + + public String generateToken(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(new Date().getTime() + expiration)) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public String getToken(HttpServletRequest request) { + String header = request.getHeader("Authorization"); + if (header != null && header.startsWith("Bearer")) { + return header.substring(7); + } + return null; + } + + public boolean validateToken(String token) { + if (token != null) { + try { + getTokenBody(token); + return true; + } catch(Exception e) { + System.out.println("Error: " + e); + } + } + return false; + } +} diff --git a/src/main/java/com/example/demo/security/LoginRequest.java b/src/main/java/com/example/demo/security/LoginRequest.java new file mode 100644 index 000000000..b30c68be2 --- /dev/null +++ b/src/main/java/com/example/demo/security/LoginRequest.java @@ -0,0 +1,23 @@ +package com.example.demo.security; + +public class LoginRequest { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/com/example/demo/security/LoginResponse.java b/src/main/java/com/example/demo/security/LoginResponse.java new file mode 100644 index 000000000..10885d878 --- /dev/null +++ b/src/main/java/com/example/demo/security/LoginResponse.java @@ -0,0 +1,92 @@ +package com.example.demo.security; + +import com.example.demo.models.Channel; +import com.example.demo.models.Message; + +import java.util.List; + +public class LoginResponse { + + private Long id; + private String token; + private String firstName; + private String lastName; + private String username; + private String email; + private List channels; + + public LoginResponse(Long id, String token, String firstName, String lastName, String username, String email, List channels) { + this.id = id; + this.token = token; + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + this.channels = channels; + } + + public LoginResponse(Long id, String token, String firstName, String lastName, String username, String email) { + this.id = id; + this.token = token; + this.firstName = firstName; + this.lastName = lastName; + this.username = username; + this.email = email; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getChannels() { + return channels; + } + + public void setChannels(List channels) { + this.channels = channels; + } +} diff --git a/src/main/java/com/example/demo/security/WebSecurityConfiguration.java b/src/main/java/com/example/demo/security/WebSecurityConfiguration.java new file mode 100644 index 000000000..228e861b9 --- /dev/null +++ b/src/main/java/com/example/demo/security/WebSecurityConfiguration.java @@ -0,0 +1,63 @@ +package com.example.demo.security; + +import com.example.demo.service.ProfileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + private ProfileService service; + + @Autowired + private JwtGenerator jwtGenerator; + + @Autowired + private JwtAuthenticationEntryPoint entryPoint; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean JwtFilter jwtFilter() { + return new JwtFilter(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(service).passwordEncoder(passwordEncoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) + .cors().and().csrf().disable() + .exceptionHandling().authenticationEntryPoint(entryPoint) + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests().antMatchers("/profile/login/**", "/profile/register").permitAll() + .anyRequest().authenticated(); + } +} diff --git a/src/main/java/com/example/demo/service/ChannelService.java b/src/main/java/com/example/demo/service/ChannelService.java index 649bf9091..97d89c17e 100644 --- a/src/main/java/com/example/demo/service/ChannelService.java +++ b/src/main/java/com/example/demo/service/ChannelService.java @@ -1,6 +1,7 @@ package com.example.demo.service; import com.example.demo.models.Channel; +import com.example.demo.models.Profile; import com.example.demo.repository.ChannelRepo; import com.example.demo.repository.ProfileRepo; import org.springframework.beans.factory.annotation.Autowired; @@ -8,6 +9,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service public class ChannelService { @@ -36,6 +38,23 @@ public List readAllChannels() { return result; } + public List findByProfileUsername(String username) { + List allChannels = readAllChannels(); + return allChannels + .stream() + .filter(channel -> { + List profilesInChannel = channel.getProfileList(); + for (Profile profile : profilesInChannel) { + if (profile.getUsername().equals(username)) { + return true; + } + } + return false; + }) + .collect(Collectors.toList()); + + } + public Channel update(Long id, Channel channel) { Channel channelInDb = readChannel(id); channelInDb.setId(channel.getId()); diff --git a/src/main/java/com/example/demo/service/ProfileService.java b/src/main/java/com/example/demo/service/ProfileService.java index c145e0896..50076f205 100644 --- a/src/main/java/com/example/demo/service/ProfileService.java +++ b/src/main/java/com/example/demo/service/ProfileService.java @@ -3,13 +3,16 @@ import com.example.demo.models.Profile; import com.example.demo.repository.ProfileRepo; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; @Service -public class ProfileService { +public class ProfileService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @@ -17,14 +20,14 @@ public class ProfileService { @Autowired private ProfileRepo repository; - public Profile createProfile(Profile profileData) { - Profile profile = new Profile(); - profile.setFirstName(profileData.getFirstName()); - profile.setLastName(profileData.getLastName()); - profile.setUsername(profileData.getUsername()); - profile.setPassword(passwordEncoder.encode(profileData.getPassword())); - profile.setEmail(profileData.getEmail()); - profile.setChannels(profileData.getChannels()); + public Profile createProfile(Profile profile) { +// Profile profile = new Profile(); +// profile.setFirstName(profileData.getFirstName()); +// profile.setLastName(profileData.getLastName()); +// profile.setUsername(profileData.getUsername()); +// profile.setPassword(passwordEncoder.encode(profileData.getPassword())); +// profile.setEnabled(true); +// profile.setEmail(profileData.getEmail()); return repository.save(profile); } @@ -32,16 +35,33 @@ public Profile findById(Long id) { return repository.findById(id).get(); } - public List findAllProfiles() { - return repository.findAll(); + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Profile profile = repository.findByUsername(username); + if (profile == null) { + throw new UsernameNotFoundException("Profile with username " + username + " not found"); + } + return profile; } - public Profile login(String username, String password) { - Profile profileToCheckPassword = repository.findByUsername(username); - if (verifyPassword(password, profileToCheckPassword)) { - return profileToCheckPassword; + public Profile findByUsername(String username) { + Profile profile = repository.findByUsername(username); + if (profile == null) { + throw new UsernameNotFoundException("Profile with username " + username + " not found"); } - return null; + return profile; + } + + public boolean existsByUsername(String username) { + return repository.existsByUsername(username); + } + + public boolean existsByEmail(String email) { + return repository.existsByEmail(email); + } + + public List findAllProfiles() { + return repository.findAll(); } public Profile update(Profile profileData) { @@ -51,8 +71,4 @@ public Profile update(Profile profileData) { public void deleteProfileById(Long id) { repository.deleteById(id); } - - private boolean verifyPassword(String password, Profile profile) { - return passwordEncoder.matches(password, profile.getPassword()); - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0de30be77..bd00f719a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,8 @@ spring.datasource.url=jdbc:mysql://localhost:3306/chatter_box -spring.datasource.username=root +spring.datasource.username=zach spring.datasource.password=zipcode0 -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +chatter-box.app.jwtSecret=someSecretKey +chatter-box.app.jwtExpirationMs=36000000 diff --git a/src/test/java/com/example/demo/controllers/TestProfileController.java b/src/test/java/com/example/demo/controllers/TestProfileController.java index b5f2ea750..2af11d731 100644 --- a/src/test/java/com/example/demo/controllers/TestProfileController.java +++ b/src/test/java/com/example/demo/controllers/TestProfileController.java @@ -1,4 +1,55 @@ package com.example.demo.controllers; +import com.example.demo.controller.ProfileController; +import com.example.demo.models.Profile; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(controllers = ProfileController.class) public class TestProfileController { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private ProfileController controller; + + @Before + public void setUp() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper = mapper; + } + + private Profile profile = new Profile(2L, "", "Ben", "Smith", "Ben123", "secretpassword", "ben@gmail.com", true); + private Profile profile1 = new Profile("Ben123", "secretpassword"); + +// @Test +// public void registerTest() throws Exception { +// Mockito.when(controller.createProfile(Mockito.any(Profile.class))).thenReturn(ResponseEntity.class); +// +// mockMvc.perform(post("/profile/register") +// .contentType("application/json") +// .content(objectMapper.writeValueAsString(profile))) +// .andExpect(status().isOk()); +// } } diff --git a/src/test/java/com/example/demo/models/TestProfile.java b/src/test/java/com/example/demo/models/TestProfile.java index d25a09f3c..7a9b119ac 100644 --- a/src/test/java/com/example/demo/models/TestProfile.java +++ b/src/test/java/com/example/demo/models/TestProfile.java @@ -18,7 +18,7 @@ public void nullableConstructorTest() { Assert.assertNull(profile.getId()); Assert.assertNull(profile.getFirstName()); Assert.assertNull(profile.getLastName()); Assert.assertNull(profile.getUsername()); Assert.assertNull(profile.getPassword()); Assert.assertNull(profile.getEmail()); - Assert.assertNull(profile.getChannels()); Assert.assertNull(profile.getMessages()); +// Assert.assertNull(profile.getChannels()); } @Test @@ -34,13 +34,13 @@ public void constructorTest() { List expectedMessages = Stream.of(new Message(), new Message(), new Message(), new Message()).collect(Collectors.toList()); // When - Profile profile = new Profile(expectedId, expectedFirstName, expectedLastName, expectedUsername, expectedPassword, expectedEmail, expectedChannels, expectedMessages); + Profile profile = new Profile(expectedId, "", expectedFirstName, expectedLastName, expectedUsername, expectedPassword, expectedEmail, true); // Then Assert.assertEquals(expectedId, profile.getId()); Assert.assertEquals(expectedFirstName, profile.getFirstName()); Assert.assertEquals(expectedLastName, profile.getLastName()); Assert.assertEquals(expectedUsername, profile.getUsername()); Assert.assertEquals(expectedPassword, profile.getPassword()); Assert.assertEquals(expectedEmail, profile.getEmail()); - Assert.assertEquals(expectedChannels, profile.getChannels()); Assert.assertEquals(expectedMessages, profile.getMessages()); +// Assert.assertEquals(expectedChannels, profile.getChannels()); } @Test @@ -63,13 +63,12 @@ public void settersTest() { profile.setUsername(expectedUsername); profile.setPassword(expectedPassword); profile.setEmail(expectedEmail); - profile.setChannels(expectedChannels); - profile.setMessages(expectedMessages); +// profile.setChannels(expectedChannels); // Then Assert.assertEquals(expectedId, profile.getId()); Assert.assertEquals(expectedFirstName, profile.getFirstName()); Assert.assertEquals(expectedLastName, profile.getLastName()); Assert.assertEquals(expectedUsername, profile.getUsername()); Assert.assertEquals(expectedPassword, profile.getPassword()); Assert.assertEquals(expectedEmail, profile.getEmail()); - Assert.assertEquals(expectedChannels, profile.getChannels()); Assert.assertEquals(expectedMessages, profile.getMessages()); +// Assert.assertEquals(expectedChannels, profile.getChannels()); } } diff --git a/src/test/java/com/example/demo/services/TestProfileService.java b/src/test/java/com/example/demo/services/TestProfileService.java index a3de52991..bafbcd09d 100644 --- a/src/test/java/com/example/demo/services/TestProfileService.java +++ b/src/test/java/com/example/demo/services/TestProfileService.java @@ -66,20 +66,6 @@ public void findAllProfilesTest() { Assert.assertEquals(expectedProfiles, actualProfiles); } - @Test - public void loginTest() { - Profile profile = new Profile(); - String username = "test username"; - String password = "test password"; - profile.setUsername(username); profile.setPassword(password); - - Mockito.when(repository.findByUsername(username)).thenReturn(profile); - service.login(username, password); - service.login(username, password); - - Mockito.verify(repository, Mockito.times(2)).findByUsername(username); - } - @Test public void updateTest() { Profile expectedProfile = new Profile(); @@ -103,4 +89,52 @@ public void deleteByIdTest() { Mockito.verify(repository, Mockito.times(2)).deleteById(id); } + + @Test + public void existsByUsernameTest() { + Profile profile = new Profile(); + String username = "Ben"; + profile.setUsername(username); + + Mockito.when(repository.existsByUsername(username)).thenReturn(true); + boolean existsByUsername = service.existsByUsername(username); + + Assert.assertTrue(existsByUsername); + } + + @Test + public void existsByEmailTest() { + Profile profile = new Profile(); + String email = "Ben@gmail.com"; + profile.setEmail(email); + + Mockito.when(repository.existsByEmail(email)).thenReturn(true); + boolean existsByEmail = service.existsByEmail(email); + + Assert.assertTrue(existsByEmail); + } + + @Test + public void findByUsernameTest() { + Profile expectedProfile = new Profile(); + String username = "Ben"; + expectedProfile.setUsername(username); + + Mockito.when(repository.findByUsername(username)).thenReturn(expectedProfile); + Profile actualProfile = service.findByUsername(username); + + Assert.assertEquals(expectedProfile, actualProfile); + } + + @Test + public void loadByUsernameTest() { + Profile expectedProfile = new Profile(); + String username = "Ben"; + expectedProfile.setUsername(username); + + Mockito.when(repository.findByUsername(username)).thenReturn(expectedProfile); + Profile actualProfile = (Profile) service.loadUserByUsername(username); + + Assert.assertEquals(expectedProfile, actualProfile); + } } diff --git a/target/classes/application.properties b/target/classes/application.properties index 0de30be77..aae8e178e 100644 --- a/target/classes/application.properties +++ b/target/classes/application.properties @@ -1,6 +1,9 @@ spring.datasource.url=jdbc:mysql://localhost:3306/chatter_box -spring.datasource.username=root +spring.datasource.username=zach spring.datasource.password=zipcode0 -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +chatter-box.app.jwtSecret=someSecretKey +chatter-box.app.jwtExpirationMs=36000000 + diff --git a/target/classes/com/example/demo/config/ProfileConfig.class b/target/classes/com/example/demo/config/ProfileConfig.class index b88a9368c..bb215688e 100644 Binary files a/target/classes/com/example/demo/config/ProfileConfig.class and b/target/classes/com/example/demo/config/ProfileConfig.class differ diff --git a/target/classes/com/example/demo/controller/ProfileController.class b/target/classes/com/example/demo/controller/ProfileController.class index 880528534..ef3371fa9 100644 Binary files a/target/classes/com/example/demo/controller/ProfileController.class and b/target/classes/com/example/demo/controller/ProfileController.class differ diff --git a/target/classes/com/example/demo/models/Channel.class b/target/classes/com/example/demo/models/Channel.class index 37ca6a28f..5261695bf 100644 Binary files a/target/classes/com/example/demo/models/Channel.class and b/target/classes/com/example/demo/models/Channel.class differ diff --git a/target/classes/com/example/demo/models/ChannelType.class b/target/classes/com/example/demo/models/ChannelType.class new file mode 100644 index 000000000..015a40d74 Binary files /dev/null and b/target/classes/com/example/demo/models/ChannelType.class differ diff --git a/target/classes/com/example/demo/models/Message.class b/target/classes/com/example/demo/models/Message.class index 1b59deae1..48af9080c 100644 Binary files a/target/classes/com/example/demo/models/Message.class and b/target/classes/com/example/demo/models/Message.class differ diff --git a/target/classes/com/example/demo/models/Profile.class b/target/classes/com/example/demo/models/Profile.class index 523eba33e..7bb52ad81 100644 Binary files a/target/classes/com/example/demo/models/Profile.class and b/target/classes/com/example/demo/models/Profile.class differ diff --git a/target/classes/com/example/demo/repository/ProfileRepo.class b/target/classes/com/example/demo/repository/ProfileRepo.class index d94a94bdf..4a994ea8a 100644 Binary files a/target/classes/com/example/demo/repository/ProfileRepo.class and b/target/classes/com/example/demo/repository/ProfileRepo.class differ diff --git a/target/classes/com/example/demo/security/JwtAuthenticationEntryPoint.class b/target/classes/com/example/demo/security/JwtAuthenticationEntryPoint.class new file mode 100644 index 000000000..0099235e3 Binary files /dev/null and b/target/classes/com/example/demo/security/JwtAuthenticationEntryPoint.class differ diff --git a/target/classes/com/example/demo/security/JwtFilter.class b/target/classes/com/example/demo/security/JwtFilter.class new file mode 100644 index 000000000..52c4f5f08 Binary files /dev/null and b/target/classes/com/example/demo/security/JwtFilter.class differ diff --git a/target/classes/com/example/demo/security/JwtGenerator.class b/target/classes/com/example/demo/security/JwtGenerator.class new file mode 100644 index 000000000..063daf804 Binary files /dev/null and b/target/classes/com/example/demo/security/JwtGenerator.class differ diff --git a/target/classes/com/example/demo/security/LoginRequest.class b/target/classes/com/example/demo/security/LoginRequest.class new file mode 100644 index 000000000..0d7aecc46 Binary files /dev/null and b/target/classes/com/example/demo/security/LoginRequest.class differ diff --git a/target/classes/com/example/demo/security/LoginResponse.class b/target/classes/com/example/demo/security/LoginResponse.class new file mode 100644 index 000000000..b584809ce Binary files /dev/null and b/target/classes/com/example/demo/security/LoginResponse.class differ diff --git a/target/classes/com/example/demo/security/WebSecurityConfiguration.class b/target/classes/com/example/demo/security/WebSecurityConfiguration.class new file mode 100644 index 000000000..f8dffa446 Binary files /dev/null and b/target/classes/com/example/demo/security/WebSecurityConfiguration.class differ diff --git a/target/classes/com/example/demo/service/ChannelService.class b/target/classes/com/example/demo/service/ChannelService.class index e33923115..d9d28084e 100644 Binary files a/target/classes/com/example/demo/service/ChannelService.class and b/target/classes/com/example/demo/service/ChannelService.class differ diff --git a/target/classes/com/example/demo/service/ProfileService.class b/target/classes/com/example/demo/service/ProfileService.class index 0504bc6fa..dce92d078 100644 Binary files a/target/classes/com/example/demo/service/ProfileService.class and b/target/classes/com/example/demo/service/ProfileService.class differ diff --git a/target/test-classes/com/example/demo/controllers/TestProfileController.class b/target/test-classes/com/example/demo/controllers/TestProfileController.class index f6fc918ed..e1363f97c 100644 Binary files a/target/test-classes/com/example/demo/controllers/TestProfileController.class and b/target/test-classes/com/example/demo/controllers/TestProfileController.class differ diff --git a/target/test-classes/com/example/demo/models/TestProfile.class b/target/test-classes/com/example/demo/models/TestProfile.class index 57155895f..2e8c2b2b0 100644 Binary files a/target/test-classes/com/example/demo/models/TestProfile.class and b/target/test-classes/com/example/demo/models/TestProfile.class differ diff --git a/target/test-classes/com/example/demo/services/TestProfileService.class b/target/test-classes/com/example/demo/services/TestProfileService.class index ea65c92c0..e610ff590 100644 Binary files a/target/test-classes/com/example/demo/services/TestProfileService.class and b/target/test-classes/com/example/demo/services/TestProfileService.class differ