diff --git a/build.gradle b/build.gradle index 1b64a67..b7e2700 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,18 @@ repositories { } dependencies { -// implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-groovy-templates' implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.12.4' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' developmentOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' + runtimeOnly 'com.h2database:h2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation group: 'org.springframework.security', name: 'spring-security-core', version: '6.4.1' + implementation group: 'com.auth0', name: 'java-jwt', version: '4.4.0' } tasks.named('test') { diff --git a/settings.gradle b/settings.gradle index 14fcfce..74bece5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'toBeNamed' +rootProject.name = 'TaskFlow' diff --git a/src/main/java/com/example/tobenamed/ToBeNamedApplication.java b/src/main/java/com/example/taskflow/TaskFlow.java similarity index 60% rename from src/main/java/com/example/tobenamed/ToBeNamedApplication.java rename to src/main/java/com/example/taskflow/TaskFlow.java index 0554d6e..48a550d 100644 --- a/src/main/java/com/example/tobenamed/ToBeNamedApplication.java +++ b/src/main/java/com/example/taskflow/TaskFlow.java @@ -1,13 +1,13 @@ -package com.example.tobenamed; +package com.example.taskflow; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class ToBeNamedApplication { +public class TaskFlow { public static void main(String[] args) { - SpringApplication.run(ToBeNamedApplication.class, args); + SpringApplication.run(TaskFlow.class, args); } } diff --git a/src/main/java/com/example/taskflow/config/JwtAuthenticationFilter.java b/src/main/java/com/example/taskflow/config/JwtAuthenticationFilter.java new file mode 100644 index 0000000..599cff7 --- /dev/null +++ b/src/main/java/com/example/taskflow/config/JwtAuthenticationFilter.java @@ -0,0 +1,37 @@ +package com.example.taskflow.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + + @Autowired + public JwtAuthenticationFilter(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authorizationHeader = request.getHeader("Authorization"); + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); // Remove "Bearer " prefix + if (jwtUtil.validateToken(token)) { + String username = jwtUtil.extractUsername(token); + // Normally set user details in the security context here + } + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/example/taskflow/config/JwtUtil.java b/src/main/java/com/example/taskflow/config/JwtUtil.java new file mode 100644 index 0000000..6495e92 --- /dev/null +++ b/src/main/java/com/example/taskflow/config/JwtUtil.java @@ -0,0 +1,41 @@ +package com.example.taskflow.config; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JwtUtil { + private final String SECRET_KEY = "mySecretKey"; // here should be something coming from for example parameter store + private final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 hours + + private final Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY); + + public String generateToken(String username) { + return JWT.create() + .withSubject(username) + .withIssuedAt(new Date()) + .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .sign(algorithm); + } + + public boolean validateToken(String token) { + try { + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(token); + return true; + } catch (JWTVerificationException e) { + return false; // Token is invalid + } + } + + public String extractUsername(String token) { + DecodedJWT decodedJWT = JWT.require(algorithm).build().verify(token); + return decodedJWT.getSubject(); + } +} diff --git a/src/main/java/com/example/taskflow/config/SecurityConfig.java b/src/main/java/com/example/taskflow/config/SecurityConfig.java new file mode 100644 index 0000000..15a04ad --- /dev/null +++ b/src/main/java/com/example/taskflow/config/SecurityConfig.java @@ -0,0 +1,24 @@ +package com.example.taskflow.config; + +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.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class SecurityConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + protected void configure(HttpSecurity http) throws Exception { +// http.csrf().disable() +// .authorizeRequests() +// .antMatchers("/register").permitAll() +// .antMatchers("/login").permitAll() +// .antMatchers("/logout").permitAll + } +} diff --git a/src/main/java/com/example/taskflow/controller/UserController.java b/src/main/java/com/example/taskflow/controller/UserController.java new file mode 100644 index 0000000..f9a7d39 --- /dev/null +++ b/src/main/java/com/example/taskflow/controller/UserController.java @@ -0,0 +1,55 @@ +package com.example.taskflow.controller; + +import com.example.taskflow.model.dto.UserLoginRequest; +import com.example.taskflow.model.dto.UserRegisterRequest; +import com.example.taskflow.model.dto.UserResponse; +import com.example.taskflow.model.dto.UserUpdateRequest; +import com.example.taskflow.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +public class UserController { + private final UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody UserRegisterRequest userRequest) { + userService.register(userRequest); + return ResponseEntity.ok().build(); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody UserLoginRequest userLoginRequest) { + userService.login(userLoginRequest); + return ResponseEntity.ok().build(); + } + + @PostMapping("/logout") + public ResponseEntity logout() { + userService.logout(); + return ResponseEntity.ok().build(); + } + + @GetMapping("/profile") + public ResponseEntity getProfile() { + return ResponseEntity.ok(userService.getProfile()); + } + + @PutMapping("/profile") + public ResponseEntity updateProfile(@RequestBody UserUpdateRequest userUpdateRequest) { + userService.updateProfile(userUpdateRequest); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/profile") + public ResponseEntity deleteProfile() { + userService.deleteProfile(); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/taskflow/mapper/UserMapper.java b/src/main/java/com/example/taskflow/mapper/UserMapper.java new file mode 100644 index 0000000..0b7935f --- /dev/null +++ b/src/main/java/com/example/taskflow/mapper/UserMapper.java @@ -0,0 +1,11 @@ +package com.example.taskflow.mapper; + +import com.example.taskflow.model.User; +import com.example.taskflow.model.dto.UserRegisterRequest; +import org.springframework.security.core.userdetails.User.UserBuilder; + +public class UserMapper { + public static User map(UserRegisterRequest userRequest) { + return new User(userRequest.username(), userRequest.password(), userRequest.email()); + } +} diff --git a/src/main/java/com/example/taskflow/model/User.java b/src/main/java/com/example/taskflow/model/User.java new file mode 100644 index 0000000..32b8369 --- /dev/null +++ b/src/main/java/com/example/taskflow/model/User.java @@ -0,0 +1,71 @@ +package com.example.taskflow.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +import java.util.Objects; + +@Entity(name = "users") +public class User { + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + private Long id; + private String username; + private String password; + private String email; + + public User() { + } + + public User(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + 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; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(username, user.username) && Objects.equals(password, user.password) && Objects.equals(email, user.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, password, email); + } +} diff --git a/src/main/java/com/example/taskflow/model/dto/UserLoginRequest.java b/src/main/java/com/example/taskflow/model/dto/UserLoginRequest.java new file mode 100644 index 0000000..48f5c88 --- /dev/null +++ b/src/main/java/com/example/taskflow/model/dto/UserLoginRequest.java @@ -0,0 +1,4 @@ +package com.example.taskflow.model.dto; + +public record UserLoginRequest(String username, String password) { +} diff --git a/src/main/java/com/example/taskflow/model/dto/UserRegisterRequest.java b/src/main/java/com/example/taskflow/model/dto/UserRegisterRequest.java new file mode 100644 index 0000000..c0d223b --- /dev/null +++ b/src/main/java/com/example/taskflow/model/dto/UserRegisterRequest.java @@ -0,0 +1,4 @@ +package com.example.taskflow.model.dto; + +public record UserRegisterRequest(String username, String password, String email) { +} diff --git a/src/main/java/com/example/taskflow/model/dto/UserResponse.java b/src/main/java/com/example/taskflow/model/dto/UserResponse.java new file mode 100644 index 0000000..395f8fd --- /dev/null +++ b/src/main/java/com/example/taskflow/model/dto/UserResponse.java @@ -0,0 +1,4 @@ +package com.example.taskflow.model.dto; + +public record UserResponse(String username, String email) { +} diff --git a/src/main/java/com/example/taskflow/model/dto/UserUpdateRequest.java b/src/main/java/com/example/taskflow/model/dto/UserUpdateRequest.java new file mode 100644 index 0000000..a755db9 --- /dev/null +++ b/src/main/java/com/example/taskflow/model/dto/UserUpdateRequest.java @@ -0,0 +1,4 @@ +package com.example.taskflow.model.dto; + +public record UserUpdateRequest(String username, String email) { +} diff --git a/src/main/java/com/example/taskflow/repository/UserRepository.java b/src/main/java/com/example/taskflow/repository/UserRepository.java new file mode 100644 index 0000000..4cad373 --- /dev/null +++ b/src/main/java/com/example/taskflow/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.example.taskflow.repository; + +import com.example.taskflow.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/com/example/taskflow/service/UserService.java b/src/main/java/com/example/taskflow/service/UserService.java new file mode 100644 index 0000000..da2041f --- /dev/null +++ b/src/main/java/com/example/taskflow/service/UserService.java @@ -0,0 +1,63 @@ +package com.example.taskflow.service; + +import com.example.taskflow.mapper.UserMapper; +import com.example.taskflow.model.User; +import com.example.taskflow.model.dto.UserLoginRequest; +import com.example.taskflow.model.dto.UserRegisterRequest; +import com.example.taskflow.model.dto.UserResponse; +import com.example.taskflow.model.dto.UserUpdateRequest; +import com.example.taskflow.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; + +@Service +public class UserService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Autowired + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + + public void register(UserRegisterRequest userRequest) { + if (userRepository.findByUsername(userRequest.username()).isPresent()) { + throw new IllegalArgumentException("Username already exists"); + } + + User user = UserMapper.map(userRequest); + userRepository.save(user); + } + + public void login(UserLoginRequest userLoginRequest) { + Optional userOpt = userRepository.findByUsername(userLoginRequest.username()); + if (userOpt.isEmpty() || !passwordEncoder.matches(userLoginRequest.password(), userOpt.get().getPassword())) { + throw new IllegalArgumentException("Invalid username or password"); + } + + // Handle login logic, e.g., issue a JWT token (if needed) + } + + public void logout() { + // Handle logout logic (if session-based authentication is used) + } + + public UserResponse getProfile() { + // Fetch and return the currently logged-in user's profile +// return new UserResponse(/* populate with user data */); + return null; + } + + public void updateProfile(UserUpdateRequest userUpdateRequest) { + // Update the current user's profile + } + + public void deleteProfile() { + // Delete the current user's profile + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6a7ff46..4e56790 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,10 @@ -spring.application.name=toBeNamed +spring.application.name=TaskFlow +spring.security.user.name=admin +spring.security.user.password=admin +spring.datasource.url=jdbc:h2:mem:test +spring.datasource.password=sa +spring.datasource.username=sa +spring.h2.console.enabled=true +spring.jpa.defer-datasource-initialization=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file diff --git a/src/test/java/com/example/taskflow/ToBeNamedApplicationTests.java b/src/test/java/com/example/taskflow/ToBeNamedApplicationTests.java new file mode 100644 index 0000000..be7a3fe --- /dev/null +++ b/src/test/java/com/example/taskflow/ToBeNamedApplicationTests.java @@ -0,0 +1,12 @@ +//package com.example.taskflow; +// +//import org.junit.jupiter.api.Test; +//import org.springframework.boot.test.context.SpringBootTest; +// +//@SpringBootTest +//class TaskFlowTests { +// +// @Test +// void contextLoads() { +// } +//} diff --git a/src/test/java/com/example/tobenamed/ToBeNamedApplicationTests.java b/src/test/java/com/example/tobenamed/ToBeNamedApplicationTests.java deleted file mode 100644 index 961ceee..0000000 --- a/src/test/java/com/example/tobenamed/ToBeNamedApplicationTests.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.tobenamed; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ToBeNamedApplicationTests { - - @Test - void contextLoads() { - - } -//ndsajk -}