Skip to content

Commit

Permalink
Merge pull request #33 from neuefische/Feat/add-login
Browse files Browse the repository at this point in the history
Feat/add-login
  • Loading branch information
esgoet authored Aug 23, 2024
2 parents b68bf60 + 03f22a1 commit 61e2a08
Show file tree
Hide file tree
Showing 19 changed files with 300 additions and 56 deletions.
9 changes: 9 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@
<version>4.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.github.esgoet.backend.security;

import com.github.esgoet.backend.user.models.User;
import com.github.esgoet.backend.user.services.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;

@GetMapping("/me")
public User getUser(@AuthenticationPrincipal OAuth2User user) {
return userService.getUserByGitHubId(user.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.github.esgoet.backend.security;

import com.github.esgoet.backend.user.dto.UserDto;
import com.github.esgoet.backend.user.models.User;
import com.github.esgoet.backend.user.models.UserNotFoundException;
import com.github.esgoet.backend.user.services.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;

import java.time.LocalDate;
import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final UserService userService;

@Value("${app.url}")
private String appUrl;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(a -> a
// .requestMatchers("/api/books").authenticated()
// .requestMatchers("/api/books/**").authenticated()
.anyRequest().permitAll()
)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
.exceptionHandling(e -> e
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.oauth2Login(o -> o.defaultSuccessUrl(appUrl))
.logout(l -> l.logoutSuccessUrl(appUrl));
return http.build();
}

@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();

return request -> {
OAuth2User user = delegate.loadUser(request);
User githubUser;
try {
githubUser = userService.getUserByGitHubId(user.getName());
} catch (UserNotFoundException e) {
githubUser = userService.saveUser(new UserDto(user.getAttributes().get("login").toString(),
0,
LocalDate.now(),
0,
user.getName(),
"USER"));
}

return new DefaultOAuth2User(List.of(new SimpleGrantedAuthority(githubUser.role())), user.getAttributes(), "id");
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public record UserDto(
String userName,
int readingGoal,
LocalDate goalDate,
int readBooks
int readBooks,
String gitHubId,
String role
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public record User (
String userName,
int readingGoal,
LocalDate goalDate,
int readBooks
int readBooks,
String gitHubId,
String role
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends MongoRepository<User, String> {
Optional<User> findByGitHubId(String gitHubId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public User getUserById(String id) {
}

public User saveUser(UserDto userDto) {
User userToSave = new User(idService.randomId(), userDto.userName(), userDto.readingGoal(), userDto.goalDate(), userDto.readBooks());
User userToSave = new User(idService.randomId(), userDto.userName(), userDto.readingGoal(), userDto.goalDate(), userDto.readBooks(), userDto.gitHubId(), userDto.role());
return userRepository.save(userToSave);
}

Expand All @@ -37,11 +37,14 @@ public User updateUser(String id, UserDto updatedUser) {
.withGoalDate(updatedUser.goalDate())
.withReadBooks(updatedUser.readBooks())
.withReadingGoal(updatedUser.readingGoal());

return userRepository.save(user);
}

public void deleteUser(String id) {
userRepository.deleteById(id);
}

public User getUserByGitHubId(String gitHubId) {
return userRepository.findByGitHubId(gitHubId).orElseThrow(() -> new UserNotFoundException("No user found with GitHub id: " + gitHubId));
}
}
4 changes: 4 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
spring.application.name=backend
spring.data.mongodb.uri=${MONGO_DB_URI}
spring.security.oauth2.client.registration.github.client-id=${GITHUB_ID}
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_SECRET}
spring.security.oauth2.client.registration.github.scope=none
app.url=${APP_URL}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;

Expand All @@ -33,6 +34,7 @@ class BookControllerIntegrationTest {
private final LocalDate createdDate = LocalDate.parse("2024-08-22");

@Test
@WithMockUser
void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Exception {

mockMvc.perform(get("/api/books"))
Expand All @@ -43,6 +45,7 @@ void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Exception {

@DirtiesContext
@Test
@WithMockUser
void getBook_Test_whenIdExists() throws Exception {
//GIVEN
bookRepository.save(new Book("1", "George Orwell", "1984", Genre.THRILLER, "this is a description", "123456isbn", "https://linkToCover", 3,localDate, ReadingStatus.TO_BE_READ, createdDate));
Expand All @@ -69,6 +72,7 @@ void getBook_Test_whenIdExists() throws Exception {

@Test
@DirtiesContext
@WithMockUser
void addABookTest_whenNewBookExists_thenReturnNewBook() throws Exception {
// GIVEN

Expand Down Expand Up @@ -105,6 +109,7 @@ void addABookTest_whenNewBookExists_thenReturnNewBook() throws Exception {

@DirtiesContext
@Test
@WithMockUser
void getBook_Test_whenIdDoesNotExists() throws Exception {
//WHEN
mockMvc.perform(get("/api/books/1"))
Expand All @@ -120,6 +125,7 @@ void getBook_Test_whenIdDoesNotExists() throws Exception {
}

@Test
@WithMockUser
void deleteBook() throws Exception {

bookRepository.save(new Book("1", "Simon", "HowToDeleteBooksFast", Genre.SCIENCE, "description", "12345678", "https://linkToCover", 3,localDate, ReadingStatus.TO_BE_READ, createdDate));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.esgoet.backend.security;

import com.github.esgoet.backend.user.models.User;
import com.github.esgoet.backend.user.repositories.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDate;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class AuthControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
UserRepository userRepository;

@Test
@DirtiesContext
void getLoggedInUserTest() throws Exception {
userRepository.save(new User("1","esgoet", 0, LocalDate.now(), 0, "123", "USER"));

mockMvc.perform(get("/api/auth/me")
.with(oidcLogin().idToken(token -> token.subject("123"))
.userInfoToken(token -> token.claim("login", "esgoet"))))
.andExpect(status().isOk())
.andExpect(content().json("""
{
"userName": "esgoet",
"readingGoal": 0,
"readBooks": 0,
"gitHubId": "123",
"role": "USER"
}
"""))
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.goalDate").exists());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.servlet.MockMvc;

Expand All @@ -26,6 +27,7 @@ class UserControllerIntegrationTest {
private final LocalDate goalDate = LocalDate.parse("2024-12-31");

@Test
@WithMockUser
void getUsersTest() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
Expand All @@ -34,9 +36,10 @@ void getUsersTest() throws Exception {

@DirtiesContext
@Test
@WithMockUser
void getUserByIdTest_whenIdExists() throws Exception{
//GIVEN
userRepository.save(new User("1","esgoet", 6, goalDate, 0));
userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER"));
//WHEN
mockMvc.perform(get("/api/users/1"))
//THEN
Expand All @@ -47,12 +50,15 @@ void getUserByIdTest_whenIdExists() throws Exception{
"userName": "esgoet",
"readingGoal": 6,
"goalDate": "2024-12-31",
"readBooks": 0
"readBooks": 0,
"gitHubId": "123",
"role": "USER"
}
"""));
}

@Test
@WithMockUser
void getUserByIdTest_whenIdDoesNotExist() throws Exception {
//WHEN
mockMvc.perform(get("/api/users/1"))
Expand All @@ -69,6 +75,7 @@ void getUserByIdTest_whenIdDoesNotExist() throws Exception {

@DirtiesContext
@Test
@WithMockUser
void addUserTest() throws Exception {
//WHEN
mockMvc.perform(post("/api/users")
Expand All @@ -78,7 +85,9 @@ void addUserTest() throws Exception {
"userName": "esgoet",
"readingGoal": 6,
"goalDate": "2024-12-31",
"readBooks": 0
"readBooks": 0,
"gitHubId": "123",
"role": "USER"
}
"""))
.andExpect(status().isOk())
Expand All @@ -87,17 +96,20 @@ void addUserTest() throws Exception {
"userName": "esgoet",
"readingGoal": 6,
"goalDate": "2024-12-31",
"readBooks": 0
"readBooks": 0,
"gitHubId": "123",
"role": "USER"
}
"""))
.andExpect(jsonPath("$.id").exists());
}

@DirtiesContext
@Test
@WithMockUser
void updateUserTest() throws Exception {
//GIVEN
userRepository.save(new User("1","esgoet", 6, goalDate, 0));
userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER"));

//WHEN
mockMvc.perform(put("/api/users/1")
Expand All @@ -107,7 +119,9 @@ void updateUserTest() throws Exception {
"userName": "esgoet",
"readingGoal": 6,
"goalDate": "2024-12-31",
"readBooks": 1
"readBooks": 1,
"gitHubId": "123",
"role": "USER"
}
"""))
//THEN
Expand All @@ -118,16 +132,19 @@ void updateUserTest() throws Exception {
"userName": "esgoet",
"readingGoal": 6,
"goalDate": "2024-12-31",
"readBooks": 1
"readBooks": 1,
"gitHubId": "123",
"role": "USER"
}
"""));
}

@DirtiesContext
@Test
@WithMockUser
void deleteUserTest() throws Exception {
//GIVEN
userRepository.save(new User("1","esgoet", 6, goalDate, 0));
userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER"));

//WHEN
mockMvc.perform(delete("/api/users/1"))
Expand Down
Loading

0 comments on commit 61e2a08

Please sign in to comment.