6 Commits

Author SHA1 Message Date
Kshitij c20b33a305 Deleted tmp folder. This branch is now on hold. tmp folders are no longer being produced. All APIs hopefully work in this one, didn't test tho 🤞 2025-04-17 23:30:52 +05:30
vedang29 a1dc9a840e Removed tmp folder while downloading 2025-04-15 22:47:28 +05:30
vedang29 508405077d Removed tmp folder while uploading 2025-04-15 22:35:44 +05:30
vedang29 36acd75eb3 Merge pull request #2 from kshitij-ka/main 2025-04-15 21:44:05 +05:30
Kshitij 5225174d51 Changed port from 8081 to 8080. 2025-04-15 21:24:17 +05:30
Kshitij 471d03d0b6 Deleted empty test file. 2025-04-15 21:24:02 +05:30
46 changed files with 995 additions and 1893 deletions
-3
View File
@@ -31,6 +31,3 @@ build/
### VS Code ###
.vscode/
### apach-maven binary ###
apache-maven-3.9.6
-34
View File
@@ -1,34 +0,0 @@
## BACKEND ##
# Base image
FROM debian:12-slim
# Metadata
LABEL maintainer="kshitijka"
LABEL version=1.0
LABEL description="Skycrate is a web based file management system that uses Hadoop as filesystem."
# Update & upgrade & install & rm
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y openjdk-17-jdk && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -s /bin/bash skycrateBack
# Create work dir
RUN mkdir /app
RUN chown -R skycrateBack:skycrateBack /app
COPY ./target/ /app
WORKDIR /app
# Create temp download directory
RUN mkdir -p /Skycrate/downloaded/
RUN chown -R skycrateBack:skycrateBack /Skycrate /Skycrate/downloaded/
# Switch user
USER skycrateBack
EXPOSE 8081
CMD ["java", "-jar", "/app/skycrateBackend-0.0.1-SNAPSHOT.jar"]
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Vendored Executable → Regular
View File
+175 -138
View File
@@ -1,153 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.skycrate.backend</groupId>
<artifactId>skycrateBackend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>skycrateBackend</name>
<description>Cloud Storage App using HDFS</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<groupId>com.skycrate.backend</groupId>
<artifactId>skycrateBackend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>skycrateBackend</name>
<description>Cloud Storage App using HDFS</description>
<properties>
<java.version>17</java.version>
</properties>
<!-- NEWLY ADDED DEPENDECIES-->
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Boot Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.4</version>
</dependency>
<!-- MySQL JDBC -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- Spring Boot Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.2.4</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- Hadoop HDFS -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.4.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.2.4</version>
</dependency>
<!-- Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- JSON Web Token (JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- Caffeine Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<!-- Hadoop Dependencies -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.4.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.4.1</version>
</dependency>
<build>
<plugins>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Logging (SLF4J + Logback) -->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.36</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>ch.qos.logback</groupId>-->
<!-- <artifactId>logback-classic</artifactId>-->
<!-- <version>1.2.11</version>-->
<!-- </dependency>-->
<!-- Apache Commons IO (for file operations) -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- NEWLY ADDED DEPENDENCY FOR DOWNLOAD ENDPOINT-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!-- Spring Boot Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,5 +1,7 @@
package com.skycrate.backend.skycrateBackend.config;
import java.security.AuthProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
@@ -8,48 +10,38 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
// Application-wide security configuration.
// Configures user authentication, password encoding, and authentication provider.
@Configuration
public class ApplicationConfiguration {
private final UserRepository userRepository;
public ApplicationConfiguration(UserRepository userRepository){
this.userRepository=userRepository;
public ApplicationConfiguration(UserRepository userRepository) {
this.userRepository = userRepository;
}
// Custom UserDetailsService to fetch user details by email.
}
@Bean
public UserDetailsService userDetailsService() {
UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
@Bean
BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// BCrypt password encoder with a higher strength for better security.
// Cost factor 12 is considered a good balance for production use.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
// AuthenticationProvider using DAO with custom user service and password encoder.
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// Provides the AuthenticationManager for authenticating credentials.
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{
return config.getAuthenticationManager();
}
}
@Bean
AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authprovider=new DaoAuthenticationProvider();
authprovider.setUserDetailsService(userDetailsService());
authprovider.setPasswordEncoder(passwordEncoder());
return authprovider;
}
}
@@ -1,6 +1,6 @@
package com.skycrate.backend.skycrateBackend.config;
import org.apache.hadoop.conf.Configuration; // Hadoop Configuration
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.security.UserGroupInformation;
import org.springframework.context.annotation.Bean;
@@ -8,32 +8,13 @@ import org.springframework.context.annotation.Bean;
import java.net.URI;
import java.security.PrivilegedExceptionAction;
@org.springframework.context.annotation.Configuration
public class HDFSConfig {
private static final String HDFS_URI = System.getenv("HDFS_URI"); // e.g., hdfs://namenode:9000
private static final String HDFS_USER = System.getenv("HDFS_USER"); // e.g., hdfsuser
@Bean
public FileSystem fileSystem() throws Exception {
return getHDFS();
}
public static FileSystem getHDFS() throws Exception {
if (HDFS_URI == null || HDFS_URI.isBlank()) {
throw new IllegalStateException("HDFS_URI environment variable not set.");
}
Configuration conf = new Configuration();
conf.set("fs.defaultFS", HDFS_URI);
if (HDFS_USER != null && !HDFS_USER.isBlank()) {
return UserGroupInformation.createRemoteUser(HDFS_USER)
.doAs((PrivilegedExceptionAction<FileSystem>) () ->
FileSystem.get(new URI(HDFS_URI), conf)
);
} else {
return FileSystem.get(new URI(HDFS_URI), conf);
}
conf.set("fs.defaultFS", "hdfs://192.168.29.56:9000");
return FileSystem.get(new URI("hdfs://192.168.29.56:9000"), conf);
}
}
}
@@ -1,23 +0,0 @@
package com.skycrate.backend.skycrateBackend.config;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HttpToHttpsRedirectConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> containerCustomizer() {
return factory -> {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8085); // HTTP port
connector.setSecure(false);
connector.setRedirectPort(8443); // HTTPS port
factory.addAdditionalTomcatConnectors(connector);
};
}
}
@@ -0,0 +1,77 @@
package com.skycrate.backend.skycrateBackend.config;
import java.io.IOException;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver handlerExceptionResolver;
private JwtService jwtService;
private UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtService jwtService,UserDetailsService userDetailsService,HandlerExceptionResolver handlerExceptionResolver){
this.handlerExceptionResolver=handlerExceptionResolver;
this.jwtService=jwtService;
this.userDetailsService=userDetailsService;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
final String authHeader=request.getHeader("Authorization");
if (authHeader==null || !authHeader.startsWith("Bearer")){
filterChain.doFilter(request, response);
return;
}
try {
final String userjwt=authHeader.substring(7);
final String userEmail=jwtService.extractUsername(userjwt);
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
if(userEmail!=null && authentication==null){
UserDetails userDetails=this.userDetailsService.loadUserByUsername(userEmail);
if (jwtService.isTokenValid(userjwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
catch (Exception err) {
handlerExceptionResolver.resolveException(request, response, null, err);
}
}
}
@@ -1,52 +1,23 @@
package com.skycrate.backend.skycrateBackend.config;
// package com.skycrate.backend.skycrateBackend.config;
import com.skycrate.backend.skycrateBackend.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
// import org.springframework.security.config.annotation.web.builders.HttpSecurity;
// import org.springframework.security.web.SecurityFilterChain;
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
// @Configuration
// public class SecurityConfig {
public SecurityConfig(AuthenticationProvider authenticationProvider,
JwtAuthenticationFilter jwtAuthenticationFilter) {
this.authenticationProvider = authenticationProvider;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http
// .csrf(csrf -> csrf.disable()) // Disable CSRF for testing APIs
// .authorizeHttpRequests(auth -> auth
// .requestMatchers("/api/hdfs/**").permitAll() // Allow HDFS endpoints
// .anyRequest().authenticated() // Everything else needs auth
// );
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/login", "/api/auth/register", "/actuator/**").permitAll()
.requestMatchers(HttpMethod.GET, "/public/**").permitAll()
.anyRequest().authenticated()
)
.requiresChannel(channel -> channel
.anyRequest().requiresSecure()
)
.headers(headers -> headers
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
// Spring Security 6+ no longer supports xss.block(true), so we just enable or disable it.
.xssProtection(xss -> xss.disable())
.frameOptions(frame -> frame.deny())
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// return http.build();
// }
// }
@@ -0,0 +1,105 @@
package com.skycrate.backend.skycrateBackend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfiguration(
JwtAuthenticationFilter jwtAuthenticationFilter,
AuthenticationProvider authenticationProvider
) {
this.authenticationProvider = authenticationProvider;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
// @Bean
// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// http.csrf()
// .disable()
// .authorizeHttpRequests()
// .requestMatchers("/api/hdfs/**") // Specific API endpoints that don't require authentication
// .permitAll()
// .requestMatchers("/api/**") // Other endpoints that should be open
// .permitAll()
// .anyRequest()
// .authenticated() // All other requests require authentication
// .and()
// .sessionManagement()
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// .and()
// .authenticationProvider(authenticationProvider)
// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
//
// return http.build();
// }
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/hdfs/**", "/api/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.csrf(csrf -> csrf.disable())
.cors(cors -> {}) // 🔥 This line enables CORS and connects to your CorsConfigurationSource bean
.build();
}
// @Bean
// CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration configuration = new CorsConfiguration();
//
// configuration.setAllowedOrigins(List.of("*"));
// configuration.setAllowedMethods(List.of("GET", "PUT", "DELETE", "POST"));
// configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
//
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//
// source.registerCorsConfiguration("/**", configuration);
//
// return source;
// }
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 🔥 Allow all origins (wildcard) safely with credentials
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
configuration.setAllowCredentials(true); // Needed for cookies / Authorization headers
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
@@ -1,120 +1,58 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.dto.LoginRequest;
import com.skycrate.backend.skycrateBackend.dto.LoginResponse;
import org.springframework.web.bind.annotation.RestController;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshRequest;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshResponse;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.security.TokenBlacklistService;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.responses.LoginResponse;
import com.skycrate.backend.skycrateBackend.services.AuthenticationService;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import com.skycrate.backend.skycrateBackend.services.RateLimiterService;
import com.skycrate.backend.skycrateBackend.services.RefreshTokenService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping("/api")
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authManager;
private final JwtService jwtService;
private final UserRepository userRepository;
private final RefreshTokenService refreshTokenService;
private final TokenBlacklistService tokenBlacklistService;
private final RateLimiterService rateLimiterService;
private final AuthenticationService authenticationService;
private AuthenticationService authenticationService;
public AuthController(
AuthenticationManager authManager,
JwtService jwtService,
UserRepository userRepository,
RefreshTokenService refreshTokenService,
TokenBlacklistService tokenBlacklistService,
RateLimiterService rateLimiterService,
AuthenticationService authenticationService
) {
this.authManager = authManager;
this.jwtService = jwtService;
this.userRepository = userRepository;
this.refreshTokenService = refreshTokenService;
this.tokenBlacklistService = tokenBlacklistService;
this.rateLimiterService = rateLimiterService;
this.authenticationService = authenticationService;
public AuthController(JwtService jwtService,AuthenticationService authenticationService){
this.jwtService=jwtService;
this.authenticationService=authenticationService;
}
// New Register Endpoint
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterUserDto request) {
User user = authenticationService.signUp(request);
return ResponseEntity.ok("User registered successfully with username: " + user.getUsername());
@GetMapping("/test")
public String teString(@RequestParam String param) {
return new String();
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) {
String ip = servletRequest.getRemoteAddr();
public ResponseEntity<LoginResponse> LoginController(@RequestBody LoginUserDto entity) {
if (rateLimiterService.isBlocked(ip)) {
return ResponseEntity.status(429).body("Too many login attempts. Please try again later.");
}
try {
authManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())
);
} catch (Exception ex) {
rateLimiterService.recordFailedAttempt(ip);
return ResponseEntity.status(401).body("Invalid credentials.");
}
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
rateLimiterService.resetAttempts(ip);
String accessToken = jwtService.generateToken(user);
RefreshToken refreshToken = refreshTokenService.createRefreshToken(user);
return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken.getToken()));
User authenticatedUser=authenticationService.authenticate(entity);
String jwtToken=jwtService.generateToken(authenticatedUser);
LoginResponse loginResponse=new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirtationTime());
return ResponseEntity.ok(loginResponse);
}
@PostMapping("/signup")
public ResponseEntity<User> register(@RequestBody RegisterUserDto entity) {
User registeredUser=authenticationService.signUp(entity);
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return ResponseEntity.badRequest().body("Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
tokenBlacklistService.blacklistToken(token);
String email = jwtService.extractUsername(token);
userRepository.findByEmail(email).ifPresent(refreshTokenService::deleteByUser);
return ResponseEntity.ok("Logged out successfully");
return ResponseEntity.ok(registeredUser);
}
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody TokenRefreshRequest request) {
String requestToken = request.getRefreshToken();
return refreshTokenService.findByToken(requestToken)
.map(token -> {
if (refreshTokenService.isExpired(token)) {
return ResponseEntity.status(403).body("Refresh token expired");
}
User user = token.getUser();
String newAccessToken = jwtService.generateToken(user);
return ResponseEntity.ok(new TokenRefreshResponse(newAccessToken, requestToken));
})
.orElseGet(() -> ResponseEntity.status(403).body("Invalid refresh token"));
}
}
}
@@ -1,72 +0,0 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.services.FileService;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/files")
public class FileController {
private final FileService fileService;
private final JwtService jwtService;
public FileController(FileService fileService, JwtService jwtService) {
this.fileService = fileService;
this.jwtService = jwtService;
}
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(
@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
try {
String token = extractToken(request);
String username = jwtService.extractUsername(token);
fileService.uploadEncryptedFile(username, file.getBytes(), file.getOriginalFilename());
return ResponseEntity.ok("File uploaded and encrypted successfully.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Upload failed: " + e.getMessage());
}
}
@GetMapping("/download/{filename}")
public ResponseEntity<?> downloadFile(
@PathVariable String filename,
@RequestParam("password") String password,
HttpServletRequest request
) {
try {
String token = extractToken(request);
String username = jwtService.extractUsername(token);
byte[] decryptedData = fileService.downloadDecryptedFile(username, password, filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.contentLength(decryptedData.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(decryptedData);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("File download failed: " + e.getMessage());
}
}
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("Missing or invalid Authorization header");
}
return authHeader.substring(7);
}
}
@@ -1,14 +1,11 @@
package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.dto.ResponseDTO;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.services.HDFSOperations;
import com.skycrate.backend.skycrateBackend.utils.KeyUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FileSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -20,7 +17,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
@@ -28,23 +24,17 @@ import java.util.List;
import org.springframework.core.io.FileSystemResource; // For FileSystemResource
import org.springframework.core.io.Resource; // For Resource
import org.springframework.http.HttpHeaders; // For HttpHeaders
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File; // For java.io.File
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPrivateKeyForUser;
import static com.skycrate.backend.skycrateBackend.utils.KeyUtil.getPublicKeyForUser;
@RestController
@RequestMapping("/api/hdfs")
public class HDFScontroller {
private final HDFSOperations hdfsOperations;
@Autowired
private UserRepository userRepository;
@Autowired
public HDFScontroller(HDFSOperations hdfsOperations) {
this.hdfsOperations = hdfsOperations;
@@ -61,186 +51,107 @@ public class HDFScontroller {
return new ResponseDTO("Failed to create folder: " + e.getMessage(), false);
}
}
@PostMapping("/uploadFile")
public ResponseDTO uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam String hdfsPath,
@RequestParam String uploadedFileName,
@RequestParam String username) {
try {
// Retrieve the user from the database using the username
User user = userRepository.findByUsername(username).orElseThrow(() -> new RuntimeException("User not found"));
// Get the public key from the user entity
byte[] publicKeyBytes = user.getPublicKey();
PublicKey publicKey = RSAKeyUtil.getPublicKeyFromBytes(publicKeyBytes);
// Encrypt the file content using the public key
byte[] encryptedData = encryptFile(file, publicKey);
// Upload the encrypted file to HDFS
hdfsOperations.uploadFile(encryptedData, hdfsPath, uploadedFileName, username);
return new ResponseDTO("File uploaded successfully", true);
} catch (IOException e) {
e.printStackTrace();
return new ResponseDTO("Failed to upload file locally: " + e.getMessage(), false);
} catch (Exception e) {
e.printStackTrace();
return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false);
}
//
// @PostMapping("/uploadFile")
// public ResponseDTO uploadFile(
// @RequestParam("file") MultipartFile file,
// @RequestParam String hdfsPath,
// @RequestParam String uploadedFileName,
// @RequestParam String username) {
// try {
// // Save file locally first
// String localPath = saveFileLocally(file);
// System.out.println("File saved locally at: " + localPath);
//
// // Upload file to HDFS
// hdfsOperations.uploadFile(localPath, hdfsPath, uploadedFileName, username);
// return new ResponseDTO("File uploaded successfully", true);
// } catch (IOException e) {
// e.printStackTrace();
// return new ResponseDTO("Failed to upload file locally: " + e.getMessage(), false);
// } catch (Exception e) {
// e.printStackTrace();
// return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false);
// }
// }
@PostMapping("/uploadFile")
public ResponseDTO uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam String hdfsPath,
@RequestParam String uploadedFileName,
@RequestParam String username) {
try {
// Upload file directly to HDFS without saving locally
hdfsOperations.uploadFile(file, hdfsPath, uploadedFileName, username);
return new ResponseDTO("File uploaded successfully", true);
} catch (Exception e) {
e.printStackTrace();
return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false);
}
}
// Helper method to encrypt the file content using RSA encryption
private byte[] encryptFile(MultipartFile file, PublicKey publicKey) throws Exception {
// Step 1: Generate a random AES key
SecretKey aesKey = generateAESKey();
// Step 2: Encrypt the file data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] fileData = file.getBytes();
byte[] encryptedData = aesCipher.doFinal(fileData);
//
// private String saveFileLocally(MultipartFile file) throws IOException {
// // Create a temporary directory if it doesn't exist
// Path tmpDir = Paths.get("tmp");
// if (!Files.exists(tmpDir)) {
// Files.createDirectories(tmpDir); // Create the directory if it doesn't exist
// }
//
// Path path = tmpDir.resolve(file.getOriginalFilename());
//
// // Copy the file to the local directory
// Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
//
// return path.toString(); // Return the local path for further processing
// }
// Step 3: Encrypt the AES key with RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
// Step 4: Combine the encrypted AES key and the encrypted data
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
combined[0] = (byte) (encryptedAesKey.length >> 24);
combined[1] = (byte) (encryptedAesKey.length >> 16);
combined[2] = (byte) (encryptedAesKey.length >> 8);
combined[3] = (byte) encryptedAesKey.length;
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
return combined;
}
// Generate a random AES key
private SecretKey generateAESKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // Use 256 bits for AES
return keyGen.generateKey();
}
private String saveFileLocally(MultipartFile file) throws IOException {
// Create a temporary directory if it doesn't exist
Path tmpDir = Paths.get("tmp");
if (!Files.exists(tmpDir)) {
Files.createDirectories(tmpDir); // Create the directory if it doesn't exist
}
Path path = tmpDir.resolve(file.getOriginalFilename());
// Copy the file to the local directory
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return path.toString(); // Return the local path for further processing
}
// @PostMapping("/downloadFile")
// public ResponseEntity<?> downloadFile(
// @RequestParam String hdfsPath,
// @RequestParam String username) {
// try {
// // Define a temporary local path to download the file
// String localPath = "/app/tmp/downloaded/" + new File(hdfsPath).getName(); // Adjust the path as needed
//
// // Download the file from HDFS to the local path
// hdfsOperations.downloadFile(hdfsPath, localPath, username);
//
// // Create a File object for the downloaded file
// File file = new File(localPath);
// if (!file.exists()) {
// return ResponseEntity.status(HttpStatus.NOT_FOUND)
// .body(new ResponseDTO("File not found", false));
// }
//
// // Create a Resource from the file
// Resource resource = new FileSystemResource(file);
// return ResponseEntity.ok()
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
// .body(resource);
// } catch (Exception e) {
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
// .body(new ResponseDTO("Failed to download file: " + e.getMessage(), false));
// }
// }
@PostMapping("/downloadFile")
public ResponseEntity<Resource> downloadFile(
@RequestParam String hdfsEncPath,
public ResponseEntity<?> downloadFile(
@RequestParam String hdfsPath,
@RequestParam String username) {
try {
// Extract the file name and extension
String encFileName = new File(hdfsEncPath).getName();
String originalFileName = encFileName.replace(".enc", "");
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
String fileName = new File(hdfsPath).getName();
InputStreamResource resource = hdfsOperations.downloadFile(hdfsPath, username);
// Define local decrypted file path
String localDecryptedPath = "/SkyCrate/downloaded/" + originalFileName;
// Define HDFS paths for encrypted file
String encFilePath = "/SkyCrate/downloaded/" + encFileName;
FileSystem fs = HDFSConfig.getHDFS();
// Download encrypted file from HDFS
fs.copyToLocalFile(new org.apache.hadoop.fs.Path(hdfsEncPath), new org.apache.hadoop.fs.Path(encFilePath));
// Retrieve the RSA private key for the user
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
// Read the encrypted file content
byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
// Step 1: Extract the AES key length from the combined data
int aesKeyLength = ((encryptedFileContent[0] & 0xFF) << 24) |
((encryptedFileContent[1] & 0xFF) << 16) |
((encryptedFileContent[2] & 0xFF) << 8) |
(encryptedFileContent[3] & 0xFF);
// Step 2: Extract the encrypted AES key and encrypted data
byte[] encryptedAesKey = new byte[aesKeyLength];
byte[] encryptedData = new byte[encryptedFileContent.length - 4 - aesKeyLength];
System.arraycopy(encryptedFileContent, 4, encryptedAesKey, 0, aesKeyLength);
System.arraycopy(encryptedFileContent, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
// Step 3: Decrypt the AES key using RSA
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
// Create the AES key
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
// Step 4: Decrypt the data using AES
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// Decrypt the file content using the provided decrypt method
// byte[] decryptedFileContent = RSAKeyUtil.decrypt(encryptedFileContent, privateKey);
byte[] decryptedFileContent = aesCipher.doFinal(encryptedData);
// Write the decrypted content to the original file
Files.write(Paths.get(localDecryptedPath + "." + fileExtension), decryptedFileContent);
// Log the file creation
if (Files.exists(Paths.get(localDecryptedPath + "." + fileExtension))) {
System.out.println("File created successfully at: " + localDecryptedPath + "." + fileExtension);
} else {
System.out.println("Failed to create file at: " + localDecryptedPath + "." + fileExtension);
}
// Create the decrypted file resource
File decryptedFile = new File(localDecryptedPath + "." + fileExtension);
Resource resource = new FileSystemResource(decryptedFile);
// Return the file as a response
return ResponseEntity.ok()
.contentLength(decryptedFile.length())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + decryptedFile.getName() + "\"")
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null);
}
}
public void initializeKeysForUser(String username) {
try {
// Check if the public key file exists
Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
if (!Files.exists(publicKeyPath)) {
// Generate and store keys if they do not exist
KeyUtil.generateAndStoreKeyPair(username);
}
} catch (Exception e) {
e.printStackTrace();
.body(new ResponseDTO("Failed to download file: " + e.getMessage(), false));
}
}
@@ -275,25 +186,4 @@ public class HDFScontroller {
.body("Failed to list files: " + e.getMessage());
}
}
@GetMapping("/getUsernameByEmail")
public ResponseEntity<?> getUsernameByEmail(@RequestParam String email) {
try {
// Fetch user from the database using the provided email
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found with email: " + email));
// // Log the retrieved user object to verify the username
// System.out.println("Retrieved user: " + user.getFullname());
// Return the username as the response
return ResponseEntity.ok(user.getFullname()); // Return the username
} catch (Exception e) {
// Handle error if user is not found or other exceptions occur
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to fetch username: " + e.getMessage());
}
}
}
@@ -1,13 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
public class LoginRequest {
private String email;
private String password;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
@@ -1,17 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
public class LoginResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";
public LoginResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
// Getters
public String getAccessToken() { return accessToken; }
public String getRefreshToken() { return refreshToken; }
public String getTokenType() { return tokenType; }
}
@@ -1,46 +1,46 @@
package com.skycrate.backend.skycrateBackend.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class RegisterUserDto {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
@NotBlank(message = "Username is required")
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "Username must be alphanumeric")
private String username;
@NotBlank(message = "Full name is required")
private String fullname;
@NotBlank(message = "First name is required")
private String firstname;
@NotBlank(message = "Last name is required")
private String lastname;
// Getters
public String getEmail() { return email; }
public String getPassword() { return password; }
public String getUsername() { return username; }
public String getFullname() { return fullname; }
public String getFirstname() { return firstname; }
public String getLastname() { return lastname; }
public String getEmail() {
return email;
}
// Setters
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public void setUsername(String username) { this.username = username; }
public void setFullname(String fullname) { this.fullname = fullname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
public void setLastname(String lastname) { this.lastname = lastname; }
}
public RegisterUserDto setEmail(String email) {
this.email = email;
return this;
}
public String getPassword() {
return password;
}
public RegisterUserDto setPassword(String password) {
this.password = password;
return this;
}
public String getFirstname() {
return firstname;
}
public RegisterUserDto setFirstname(String firstname) {
this.firstname = firstname;
return this;
}
public String getLastname() {
return lastname;
}
public RegisterUserDto setLastname(String lastname) {
this.lastname = lastname;
return this;
}
}
@@ -1,33 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
public class SignupRequest {
private String username;
private String email;
private String password;
// Getters
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
// Setters
public void setUsername(String username) {
this.username = username;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -1,13 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
public class TokenRefreshRequest {
private String refreshToken;
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}
@@ -1,17 +0,0 @@
package com.skycrate.backend.skycrateBackend.dto;
public class TokenRefreshResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";
public TokenRefreshResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
// Getters
public String getAccessToken() { return accessToken; }
public String getRefreshToken() { return refreshToken; }
public String getTokenType() { return tokenType; }
}
@@ -0,0 +1,23 @@
package com.skycrate.backend.skycrateBackend.dto;
import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import java.security.KeyPair;
public class User {
private String username;
private KeyPair keyPair;
public User(String username) throws Exception {
this.username = username;
this.keyPair = EncryptionUtil.generateKeyPair();
}
public String getUsername() {
return username;
}
public KeyPair getKeyPair() {
return keyPair;
}
}
@@ -1,39 +0,0 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "file_metadata")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileMetadata {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String filePath;
@Column(nullable = false)
private String username;
@Lob
@Column(nullable = false)
private byte[] salt;
@Lob
@Column(nullable = false)
private byte[] iv;
@Lob
@Column(nullable = false, name = "encrypted_key", columnDefinition = "LONGBLOB")
private byte[] encryptedKey;
@Column(nullable = false)
private long uploadedAt;
}
@@ -1,33 +0,0 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import java.time.Instant;
@Entity
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String token;
@OneToOne
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
private Instant expiryDate;
// Getters and setters
public Long getId() { return id; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public Instant getExpiryDate() { return expiryDate; }
public void setExpiryDate(Instant expiryDate) { this.expiryDate = expiryDate; }
}
@@ -1,90 +0,0 @@
package com.skycrate.backend.skycrateBackend.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String fullname;
@Lob
private byte[] publicKey;
@Lob
private byte[] privateKey;
@Lob
@Column(nullable = false)
private byte[] privateKeySalt;
@Lob
@Column(nullable = false)
private byte[] privateKeyIv;
@Builder
public User(String email, String password, String username, String fullname,
byte[] publicKey, byte[] privateKey,
byte[] privateKeySalt, byte[] privateKeyIv) {
this.email = email;
this.password = password;
this.username = username;
this.fullname = fullname;
this.publicKey = publicKey;
this.privateKey = privateKey;
this.privateKeySalt = privateKeySalt;
this.privateKeyIv = privateKeyIv;
}
// --- UserDetails interface methods ---
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(); // No roles assigned currently
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
@@ -0,0 +1,129 @@
package com.skycrate.backend.skycrateBackend.models;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import jakarta.persistence.*;
@Table(name = "users")
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(nullable = false)
private Integer id;
@Column(nullable = false)
private String username;
/*
//Optional feature might add later
@Column(name = "verification_code")
private String verificationCode;
@Column(name ="verification_expiry")
private LocalDateTime verificationExpiry;
*/
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
public User(){
}
public User(String firstname,String lastname,String email,String password){
this.username=firstname+lastname;
this.email=email;
this.password=password;
}
@CreationTimestamp
@Column(updatable = false, name = "created_at")
private Date createdAt;
@Override
public Collection<? extends GrantedAuthority> getAuthorities(){
return List.of();
}
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setFullname(String firstname,String lastname) {
this.username=firstname+lastname;
}
public String getFullname(String firstname,String lastname){
return this.username;
}
public void setEmail(String email) {
this.email = email;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
@@ -1,10 +0,0 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long> {
Optional<FileMetadata> findByUsernameAndFilePath(String username, String filePath);
}
@@ -1,18 +0,0 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
@Transactional
@Modifying
@Query("DELETE FROM RefreshToken t WHERE t.user = :user")
void deleteByUser(User user);
}
@@ -0,0 +1,25 @@
package com.skycrate.backend.skycrateBackend.repository;
import com.skycrate.backend.skycrateBackend.dto.User;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserManager {
private Map<String, User> users = new HashMap<>();
public User getUser(String username) throws Exception {
if (!users.containsKey(username)) {
users.put(username, new User(username));
}
return users.get(username);
}
public boolean authenticate(String username, String password) {
// Implement your authentication logic here
return "admin".equals(username) && "password123".equals(password);
}
}
@@ -2,11 +2,9 @@ package com.skycrate.backend.skycrateBackend.repository;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.models.User;
public interface UserRepository extends CrudRepository<User,Integer> {
Optional<User> findByEmail(String email);
// Custom query method to find user by username
Optional<User> findByUsername(String username);
/*
// might use later
Optional<User> findByVerificationCode(String verificationCode);
@@ -1,42 +0,0 @@
package com.skycrate.backend.skycrateBackend.security;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.*;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptionService {
private static final int KEY_LENGTH = 256;
private static final int ITERATIONS = 65536;
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 12;
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
return salt;
}
public static SecretKey deriveKey(String password, byte[] salt) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return cipher.doFinal(ciphertext);
}
}
@@ -1,90 +0,0 @@
package com.skycrate.backend.skycrateBackend.security;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserRepository userRepository;
private final TokenBlacklistService tokenBlacklistService;
public JwtAuthenticationFilter(JwtService jwtService,
UserRepository userRepository,
TokenBlacklistService tokenBlacklistService) {
this.jwtService = jwtService;
this.userRepository = userRepository;
this.tokenBlacklistService = tokenBlacklistService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
if (path.startsWith("/api/auth") || path.startsWith("/actuator")) {
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (!StringUtils.hasText(authHeader) || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
// Check if token is blacklisted
if (tokenBlacklistService.isTokenBlacklisted(jwt)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Token has been blacklisted");
return;
}
try {
username = jwtService.extractUsername(jwt); // This is actually the `username`, not email
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid JWT token");
return;
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// ❗ Use username to find the user
User user = userRepository.findByUsername(username).orElse(null);
if (user != null && jwtService.isTokenValid(jwt, user)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Expired or invalid JWT");
return;
}
}
filterChain.doFilter(request, response);
}
}
@@ -1,27 +0,0 @@
package com.skycrate.backend.skycrateBackend.security;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class TokenBlacklistService {
private final Cache<String, Boolean> blacklistCache;
public TokenBlacklistService() {
this.blacklistCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public void blacklistToken(String token) {
blacklistCache.put(token, true);
}
public boolean isTokenBlacklisted(String token) {
return blacklistCache.getIfPresent(token) != null;
}
}
@@ -1,93 +1,45 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
@Service
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
public AuthenticationService(UserRepository userRepository,
AuthenticationManager authenticationManager,
PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
public AuthenticationService( UserRepository userRepository, AuthenticationManager authenticationManager , PasswordEncoder passwordEncoder){
this.userRepository=userRepository;
this.passwordEncoder=passwordEncoder;
this.authenticationManager=authenticationManager;
}
public User signUp(RegisterUserDto inputUser) {
// Generate RSA key pair
KeyPair keyPair;
try {
keyPair = RSAKeyUtil.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to generate RSA key pair", e);
}
public User signUp(RegisterUserDto inputuser){
User user=new User(inputuser.getFirstname(),inputuser.getLastname(),inputuser.getEmail(),passwordEncoder.encode(inputuser.getPassword()));
/*
User user = new User()
.setFullname(inputuser.getFirstname(),inputuser.getLastname())
.setEmail(inputuser.getEmail())
.setPassword(passwordEncoder.encode(inputuser.getPassword()));
*/
// Encrypt private key using password-derived AES key
byte[] salt = EncryptionUtil.generateSalt();
byte[] iv = EncryptionUtil.generateIv();
byte[] encryptedPrivateKey;
try {
SecretKey aesKey = EncryptionUtil.deriveKey(inputUser.getPassword().toCharArray(), salt);
encryptedPrivateKey = EncryptionUtil.encrypt(keyPair.getPrivate().getEncoded(), aesKey, iv);
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt private key", e);
}
// Create user entity with encrypted private key, salt, and iv
User user = User.builder()
.fullname(inputUser.getFirstname() + " " + inputUser.getLastname())
.username(inputUser.getUsername())
.email(inputUser.getEmail())
.password(passwordEncoder.encode(inputUser.getPassword()))
.publicKey(keyPair.getPublic().getEncoded())
.privateKey(encryptedPrivateKey)
.privateKeySalt(salt)
.privateKeyIv(iv)
.build();
// Save user
User savedUser = userRepository.save(user);
// Create HDFS directory in root with username
try {
FileSystem fs = HDFSConfig.getHDFS();
Path userDir = new Path("/" + savedUser.getUsername());
if (!fs.exists(userDir)) {
fs.mkdirs(userDir);
}
} catch (Exception e) {
throw new RuntimeException("Failed to create HDFS directory for user: " + savedUser.getUsername(), e);
}
return savedUser;
return userRepository.save(user) ;
}
public User authenticate(LoginUserDto inputUser) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(inputUser.getEmail(), inputUser.getPassword())
);
public User authenticate(LoginUserDto inputuser){
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(inputuser.getEmail()
, inputuser.getPassword()));
return userRepository.findByEmail(inputuser.getEmail()).orElseThrow();
return userRepository.findByEmail(inputUser.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
}
}
}
@@ -1,79 +1,85 @@
package com.skycrate.backend.skycrateBackend.services;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Arrays;
public class EncryptionUtil {
private static final String RSA_ALGORITHM = "RSA";
private static final String AES_ALGORITHM = "AES";
private static final int RSA_KEY_SIZE = 2048;
private static final int AES_KEY_SIZE = 256;
private static final int SALT_LENGTH = 16; // in bytes
private static final int IV_LENGTH = 16; // for AES CBC
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256; // bits
// --- AES key derivation using PBKDF2 ---
public static SecretKey deriveAESKey(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
// Generate RSA Key Pair (Public & Private)
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyGen.initialize(RSA_KEY_SIZE);
return keyGen.generateKeyPair();
}
// --- Encrypt data using AES-CBC ---
public static byte[] encrypt(byte[] data, SecretKey key, byte[] iv)
throws GeneralSecurityException {
// Encrypt data using AES (AES Key is encrypted using RSA)
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
// Step 1: Generate AES Key
SecretKey aesKey = generateAESKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Step 2: Encrypt data using AES
Cipher aesCipher = Cipher.getInstance(AES_ALGORITHM);
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encryptedData = aesCipher.doFinal(data);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// Step 3: Encrypt the AES key with RSA
Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM);
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
return cipher.doFinal(data);
// Step 4: Combine encrypted AES key and encrypted data into one array
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length];
// First 4 bytes indicate the length of the AES encrypted key
combined[0] = (byte) (encryptedAesKey.length >> 24);
combined[1] = (byte) (encryptedAesKey.length >> 16);
combined[2] = (byte) (encryptedAesKey.length >> 8);
combined[3] = (byte) encryptedAesKey.length;
// Copy AES Key and Encrypted Data into the combined array
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length);
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length);
return combined;
}
// --- Decrypt data using AES-CBC ---
public static byte[] decrypt(byte[] encryptedData, SecretKey key, byte[] iv)
throws GeneralSecurityException {
// Decrypt data using RSA (AES Key is decrypted using RSA, then used for AES decryption)
public static byte[] decrypt(byte[] encryptedCombined, PrivateKey privateKey) throws Exception {
// Step 1: Extract AES Key length from the combined data
int aesKeyLength = ((encryptedCombined[0] & 0xFF) << 24) |
((encryptedCombined[1] & 0xFF) << 16) |
((encryptedCombined[2] & 0xFF) << 8) |
(encryptedCombined[3] & 0xFF);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Step 2: Extract the encrypted AES key and encrypted data
byte[] encryptedAesKey = new byte[aesKeyLength];
byte[] encryptedData = new byte[encryptedCombined.length - 4 - aesKeyLength];
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
System.arraycopy(encryptedCombined, 4, encryptedAesKey, 0, aesKeyLength);
System.arraycopy(encryptedCombined, 4 + aesKeyLength, encryptedData, 0, encryptedData.length);
return cipher.doFinal(encryptedData);
// Step 3: Decrypt the AES key using RSA
Cipher rsaCipher = Cipher.getInstance(RSA_ALGORITHM);
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, AES_ALGORITHM);
// Step 4: Decrypt the data using AES
Cipher aesCipher = Cipher.getInstance(AES_ALGORITHM);
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
return aesCipher.doFinal(encryptedData);
}
// --- Generate random salt ---
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
return salt;
}
// --- Generate random IV ---
public static byte[] generateIV() {
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
return iv;
}
// --- Optional: Utility to base64 encode data ---
public static String encodeBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decodeBase64(String base64) {
return Base64.getDecoder().decode(base64);
// Generate a random AES key
private static SecretKey generateAESKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGORITHM);
keyGen.init(AES_KEY_SIZE);
return keyGen.generateKey();
}
}
@@ -1,114 +0,0 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.entity.FileMetadata;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.FileMetadataRepository;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.io.ByteArrayInputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
@Service
public class FileService {
private static final Logger log = LoggerFactory.getLogger(FileService.class);
private final FileMetadataRepository fileMetadataRepository;
private final UserRepository userRepository;
public FileService(FileMetadataRepository fileMetadataRepository, UserRepository userRepository) {
this.fileMetadataRepository = fileMetadataRepository;
this.userRepository = userRepository;
}
public void uploadEncryptedFile(String username, byte[] fileContent, String filename) throws Exception {
log.info("Starting upload for user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
SecretKey aesKey = EncryptionUtil.generateAESKey();
byte[] salt = EncryptionUtil.generateSalt(); // reserved for future use
byte[] iv = EncryptionUtil.generateIv();
byte[] encryptedData = EncryptionUtil.encrypt(fileContent, aesKey, iv);
PublicKey publicKey = RSAKeyUtil.decodePublicKey(user.getPublicKey());
byte[] encryptedAesKey = EncryptionUtil.encryptRSA(aesKey.getEncoded(), publicKey);
Path userDir = new Path("/" + username);
Path filePath = new Path(userDir, filename);
FileSystem fs = HDFSConfig.getHDFS();
if (!fs.exists(userDir)) {
log.info("Creating directory in HDFS: {}", userDir);
fs.mkdirs(userDir);
}
log.info("Writing encrypted file to HDFS: {}", filePath);
try (FSDataOutputStream out = fs.create(filePath, true);
ByteArrayInputStream in = new ByteArrayInputStream(encryptedData)) {
in.transferTo(out);
}
FileMetadata metadata = FileMetadata.builder()
.username(username)
.filePath(filePath.toString())
.salt(salt)
.iv(iv)
.encryptedKey(encryptedAesKey)
.uploadedAt(System.currentTimeMillis())
.build();
fileMetadataRepository.save(metadata);
log.info("Upload complete: file={} for user={}", filename, username);
} catch (Exception e) {
log.error("Error during file upload for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
}
public byte[] downloadDecryptedFile(String username, String password, String filename) throws Exception {
log.info("Download request: user={}, file={}", username, filename);
try {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found: " + username));
Path filePath = new Path("/" + username + "/" + filename);
FileMetadata metadata = fileMetadataRepository.findByUsernameAndFilePath(username, filePath.toString())
.orElseThrow(() -> new RuntimeException("File metadata not found for: " + filePath));
SecretKey derivedKey = EncryptionUtil.deriveKey(password.toCharArray(), user.getPrivateKeySalt());
byte[] decryptedPrivateKeyBytes = EncryptionUtil.decrypt(user.getPrivateKey(), derivedKey, user.getPrivateKeyIv());
PrivateKey privateKey = RSAKeyUtil.decodePrivateKey(decryptedPrivateKeyBytes);
byte[] aesKeyBytes = EncryptionUtil.decryptRSA(metadata.getEncryptedKey(), privateKey);
SecretKey aesKey = EncryptionUtil.rebuildAESKey(aesKeyBytes);
FileSystem fs = HDFSConfig.getHDFS();
byte[] encryptedData;
try (FSDataInputStream in = fs.open(filePath)) {
encryptedData = in.readAllBytes();
}
return EncryptionUtil.decrypt(encryptedData, aesKey, metadata.getIv());
} catch (Exception e) {
log.error("Download failed for user={}, file={}: {}", username, filename, e.getMessage(), e);
throw e;
}
}
}
@@ -1,16 +1,12 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.skycrate.backend.skycrateBackend.utils.KeyUtil;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.fs.*;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
@@ -18,7 +14,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -32,129 +27,97 @@ import java.util.List;
@Service
public class HDFSOperations {
private final UserRepository userRepository;
@Autowired
public HDFSOperations(UserRepository userRepository) {
this.userRepository = userRepository;
}
// public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
// public void uploadFile(String localPath, String hdfsPath, String uploadedFileName, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
// byte[] data = Files.readAllBytes(Paths.get(localPath)); // Read file as bytes
//
// // Create an InputStream from the byte array
// ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
// // Encrypt file (consider adding encryption here as needed)
// byte[] encryptedData = data;
//
// String tempFilePath = localPath + ".enc";
// Files.write(Paths.get(tempFilePath), encryptedData);
//
// // Prepare the path for HDFS
// String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
// fs.copyFromLocalFile(new Path(tempFilePath), new Path(finalHdfsPath));
//
// // Upload the file directly to HDFS from the InputStream
// Path hdfsFilePath = new Path(finalHdfsPath);
// FSDataOutputStream outputStream = fs.create(hdfsFilePath);
// IOUtils.copyBytes(inputStream, outputStream, 4096, true);
//
// Files.delete(Paths.get(tempFilePath));
// } catch (IOException e) {
// // Handle I/O exception and log the error
// throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
// throw new RuntimeException("Failed to upload file due to I/O issue: " + e.getMessage(), e);
// } catch (Exception e) {
// // Catch any other exceptions
// throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
// throw new RuntimeException("Failed to upload file: " + e.getMessage(), e);
// }
// }
//
// public void downloadFile(String hdfsEncPath, String localPathWithoutExt, String username) {
public void uploadFile(MultipartFile file, String hdfsPath, String uploadedFileName, String username) {
try {
FileSystem fs = HDFSConfig.getHDFS();
// Encrypt file data if needed (currently direct pass-through)
byte[] fileBytes = file.getBytes(); // directly get bytes from MultipartFile
byte[] encryptedData = fileBytes;
// Define final HDFS file path
String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
// Write encrypted bytes directly to HDFS
Path hdfsDestPath = new Path(finalHdfsPath);
try (FSDataOutputStream outputStream = fs.create(hdfsDestPath, true)) {
outputStream.write(encryptedData);
}
} catch (IOException e) {
throw new RuntimeException("Failed to upload file due to I/O issue: " + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException("Failed to upload file: " + e.getMessage(), e);
}
}
// public void downloadFile(String hdfsPath, String localPath, String username) {
// try {
// FileSystem fs = HDFSConfig.getHDFS();
// String tempFilePath = localPath + ".enc";
//
// // Extract file name and extension
// String encFileName = new File(hdfsEncPath).getName();
// String originalFileName = encFileName.replace(".enc", "");
// String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
// fs.copyToLocalFile(new Path(hdfsPath), new Path(tempFilePath));
//
// String fullDecryptedPath = localPathWithoutExt + "/" + originalFileName;
// String encFilePath = fullDecryptedPath + ".enc";
// String keyFilePath = fullDecryptedPath + ".key";
//
// // Download encrypted file and AES key from HDFS
// fs.copyToLocalFile(new Path(hdfsEncPath), new Path(encFilePath));
// fs.copyToLocalFile(new Path(hdfsEncPath.replace(".enc", ".key")), new Path(keyFilePath));
//
// // Read the encrypted AES key
// byte[] encryptedAesKey = Files.readAllBytes(Paths.get(keyFilePath));
// System.out.println("Length of encrypted AES key: " + encryptedAesKey.length);
//
// // Retrieve the RSA private key for the user
// User user = userRepository.findByUsername(username)
// .orElseThrow(() -> new RuntimeException("User not found"));
// PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
//
// Cipher rsaCipher = Cipher.getInstance("RSA");
// rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
// byte[] aesKeyBytes = rsaCipher.doFinal(encryptedAesKey);
//
// // Ensure valid AES key length
// if (aesKeyBytes.length != 16 && aesKeyBytes.length != 24 && aesKeyBytes.length != 32) {
// throw new RuntimeException("Invalid AES key length: " + aesKeyBytes.length + " bytes");
// }
//
// SecretKey aesKey = new SecretKeySpec(aesKeyBytes, 0, aesKeyBytes.length, "AES");
//
// // Read the encrypted file content
// byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
//
// // Decrypt the file content using AES
// Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // Specify padding
// aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
// byte[] decryptedFileContent = aesCipher.doFinal(encryptedFileContent);
//
// // Write the decrypted content to the original file
// Files.write(Paths.get(fullDecryptedPath + "." + fileExtension), decryptedFileContent);
//
// // Cleanup temporary files
// Files.deleteIfExists(Paths.get(encFilePath));
// Files.deleteIfExists(Paths.get(keyFilePath));
// byte[] encryptedData = Files.readAllBytes(Paths.get(tempFilePath));
// byte[] decryptedData = encryptedData; // Decrypt if needed
//
// Files.write(Paths.get(localPath), decryptedData);
// Files.delete(Paths.get(tempFilePath));
// } catch (IOException e) {
// // Handle I/O exception and log the error
// throw new RuntimeException("Failed to download file due to I/O issue: " + e.getMessage(), e);
// } catch (Exception e) {
// throw new RuntimeException("Failed to download or decrypt file: " + e.getMessage(), e);
// // Catch any other exceptions
// throw new RuntimeException("Failed to download file: " + e.getMessage(), e);
// }
// }
public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
public InputStreamResource downloadFile(String hdfsPath, String username) {
try {
FileSystem fs = HDFSConfig.getHDFS();
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName;
Path hdfsFilePath = new Path(finalHdfsPath);
try (FSDataOutputStream outputStream = fs.create(hdfsFilePath)) {
IOUtils.copyBytes(inputStream, outputStream, 4096, true);
}
// Read encrypted file directly from HDFS
FSDataInputStream hdfsInputStream = fs.open(new Path(hdfsPath));
// Optional: If decryption is needed, read bytes, decrypt, and wrap in ByteArrayInputStream
byte[] encryptedData = IOUtils.toByteArray(hdfsInputStream);
byte[] decryptedData = encryptedData; // Apply decryption if needed
return new InputStreamResource(new ByteArrayInputStream(decryptedData));
} catch (IOException e) {
throw new RuntimeException("Failed to upload file to HDFS: " + e.getMessage(), e);
throw new RuntimeException("Failed to stream file due to I/O issue: " + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException(e);
throw new RuntimeException("Failed to stream file: " + e.getMessage(), e);
}
}
public void downloadFile(String hdfsEncPath, String localPathWithoutExt, String username) {
try {
FileSystem fs = HDFSConfig.getHDFS();
String encFilePath = localPathWithoutExt + ".enc";
fs.copyToLocalFile(new Path(hdfsEncPath), new Path(encFilePath));
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
PrivateKey privateKey = RSAKeyUtil.getPrivateKeyFromBytes(user.getPrivateKey());
byte[] encryptedFileContent = Files.readAllBytes(Paths.get(encFilePath));
byte[] decryptedFileContent = RSAKeyUtil.decrypt(encryptedFileContent, privateKey);
Files.write(Paths.get(localPathWithoutExt), decryptedFileContent);
Files.deleteIfExists(Paths.get(encFilePath));
} catch (Exception e) {
throw new RuntimeException("Failed to download or decrypt file: " + e.getMessage(), e);
}
}
public void createFolder(String hdfsPath) {
try {
@@ -1,69 +1,83 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.entity.User;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.function.Function;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Service
public class JwtService {
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.expiration-time}")
private long expirationTime;
private long jwtExpiration;
private static final String SECRET_KEY = "PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=";
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
public String extractUsername(String token){
return extractClaim(token,Claims::getSubject);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
public <T> T extractClaim(String token,Function<Claims,T> claimsResolver){
final Claims claims=extractAllClaims(token);
return claimsResolver.apply(claims);
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(User user) {
return generateToken((UserDetails) user);
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return buildToken(extraClaims, userDetails, jwtExpiration);
}
}
public long getExpirtationTime(){
return jwtExpiration;
}
private String buildToken(Map<String,Object> extraClaims,UserDetails userDetails,long expiration){
return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}
@@ -1,23 +0,0 @@
// NEED TO IMPLEMENT SAHI SE
package com.skycrate.backend.skycrateBackend.services;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class RateLimiterService {
private final ConcurrentHashMap<String, Integer> attempts = new ConcurrentHashMap<>();
public boolean isBlocked(String ip) {
return attempts.getOrDefault(ip, 0) >= 5;
}
public void recordFailedAttempt(String ip) {
attempts.put(ip, attempts.getOrDefault(ip, 0) + 1);
}
public void resetAttempts(String ip) {
attempts.remove(ip);
}
}
@@ -1,50 +0,0 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.entity.RefreshToken;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.RefreshTokenRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
@Service
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepo;
@Value("${security.jwt.refresh-expiry-ms:604800000}") // 7 days default
private Long refreshTokenDurationMs;
public RefreshTokenService(RefreshTokenRepository refreshTokenRepo) {
this.refreshTokenRepo = refreshTokenRepo;
}
@Transactional
public RefreshToken createRefreshToken(User user) {
refreshTokenRepo.deleteByUser(user);
refreshTokenRepo.flush();
RefreshToken token = new RefreshToken();
token.setUser(user);
token.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
token.setToken(UUID.randomUUID().toString());
return refreshTokenRepo.save(token);
}
public Optional<RefreshToken> findByToken(String token) {
return refreshTokenRepo.findByToken(token);
}
public boolean isExpired(RefreshToken token) {
return token.getExpiryDate().isBefore(Instant.now());
}
@Transactional
public void deleteByUser(User user) {
refreshTokenRepo.deleteByUser(user);
}
}
@@ -1,52 +0,0 @@
package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.dto.SignupRequest;
import com.skycrate.backend.skycrateBackend.entity.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.math.BigInteger;
import java.security.MessageDigest;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public void registerUser(SignupRequest request) {
if (isPasswordPwned(request.getPassword())) {
throw new IllegalArgumentException("Password has been compromised in data breaches.");
}
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
userRepository.save(user);
}
private boolean isPasswordPwned(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(password.getBytes());
String fullHash = String.format("%040x", new BigInteger(1, hash)).toUpperCase();
String prefix = fullHash.substring(0, 5);
String suffix = fullHash.substring(5);
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("https://api.pwnedpasswords.com/range/" + prefix, String.class);
return response != null && response.contains(suffix);
} catch (Exception e) {
return false; // If API fails, allow but log in production
}
}
}
@@ -1,94 +0,0 @@
package com.skycrate.backend.skycrateBackend.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.*;
import java.security.*;
import javax.crypto.spec.PBEKeySpec;
import java.util.Arrays;
public class EncryptionUtil {
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 16;
private static final int ITERATIONS = 65536;
private static final int KEY_LENGTH = 256;
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String AES_CIPHER = "AES/CBC/PKCS5Padding";
private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final SecureRandom secureRandom = new SecureRandom();
// ------------------------ AES ------------------------
public static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
return salt;
}
public static byte[] generateIv() {
byte[] iv = new byte[IV_LENGTH];
secureRandom.nextBytes(iv);
return iv;
}
public static SecretKey deriveKey(char[] password, byte[] salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
public static SecretKey generateAESKey() throws Exception {
byte[] keyBytes = new byte[KEY_LENGTH / 8]; // 256 bits
secureRandom.nextBytes(keyBytes);
return new SecretKeySpec(keyBytes, "AES");
}
public static SecretKey rebuildAESKey(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, "AES");
}
public static byte[] encrypt(byte[] plaintext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, SecretKey key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
return cipher.doFinal(ciphertext);
}
// ------------------------ RSA ------------------------
public static byte[] encryptRSA(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
public static byte[] decryptRSA(byte[] data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
// --------- Encrypt/decrypt RSA private key using AES derived from password ---------
public static byte[] encryptPrivateKey(PrivateKey privateKey, String password, byte[] salt, byte[] iv) throws Exception {
SecretKey aesKey = deriveKey(password.toCharArray(), salt);
return encrypt(privateKey.getEncoded(), aesKey, iv);
}
public static byte[] decryptPrivateKey(byte[] encryptedPrivateKey, String password, byte[] salt, byte[] iv) throws Exception {
SecretKey aesKey = deriveKey(password.toCharArray(), salt);
return decrypt(encryptedPrivateKey, aesKey, iv);
}
}
@@ -3,37 +3,24 @@ package com.skycrate.backend.skycrateBackend.utils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class KeyUtil {
public static void generateAndStoreKeyPair(String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048); // Key size
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// Store the public key
Path publicKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
Files.write(publicKeyPath, keyPair.getPublic().getEncoded());
// Store the private key
Path privateKeyPath = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
Files.write(privateKeyPath, keyPair.getPrivate().getEncoded());
}
public static PublicKey getPublicKeyForUser(String username) throws Exception {
Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_public.key");
byte[] bytes = Files.readAllBytes(path);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
public static PrivateKey getPrivateKeyForUser(String username) throws Exception {
Path path = Paths.get("C:\\Users\\sonal\\OneDrive\\Desktop\\SkyCrate\\Skycrate\\keys", username + "_private.key");
Path path = Paths.get("keys", username + "_private.key");
byte[] bytes = Files.readAllBytes(path);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
}
public static PublicKey getPublicKeyForUser(String username) throws Exception {
Path path = Paths.get("keys", username + "_public.key");
byte[] bytes = Files.readAllBytes(path);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
}
@@ -1,73 +0,0 @@
package com.skycrate.backend.skycrateBackend.utils;
import javax.crypto.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class RSAKeyUtil {
// Generate RSA Key Pair
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048); // Key size
return generator.generateKeyPair();
}
// Convert byte array to PublicKey
public static PublicKey getPublicKeyFromBytes(byte[] publicKeyBytes) throws Exception {
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
// Convert byte array to PrivateKey
public static PrivateKey getPrivateKeyFromBytes(byte[] privateKeyBytes) throws Exception {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
// Shorthand decode aliases
public static PublicKey decodePublicKey(byte[] publicKeyBytes) throws Exception {
return getPublicKeyFromBytes(publicKeyBytes);
}
public static PrivateKey decodePrivateKey(byte[] privateKeyBytes) throws Exception {
return getPrivateKeyFromBytes(privateKeyBytes);
}
// Encrypt data using RSA (with padding)
public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
// Decrypt data using RSA (with padding)
public static byte[] decrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedData);
}
// AES key generation
public static SecretKey generateAESKey(int keySize) throws NoSuchAlgorithmException {
if (keySize != 128 && keySize != 192 && keySize != 256) {
throw new IllegalArgumentException("Invalid AES key size. Must be 128, 192, or 256 bits.");
}
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
public static byte[] encryptAESKey(SecretKey aesKey, PublicKey publicKey) throws Exception {
return encrypt(aesKey.getEncoded(), publicKey);
}
public static SecretKey decryptAESKey(byte[] encryptedAESKey, PrivateKey privateKey, int keySize) throws Exception {
byte[] decryptedKey = decrypt(encryptedAESKey, privateKey);
return new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
}
}
-9
View File
@@ -1,9 +0,0 @@
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: tomcat
+22 -19
View File
@@ -3,15 +3,13 @@ spring.application.name=skycrateBackend
spring.servlet.multipart.max-file-size=1000MB
spring.servlet.multipart.max-request-size=1000MB
spring.servlet.multipart.enabled=true
security.jwt.secret-key=PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=
security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b
security.jwt.expiration-time=3600000
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.datasource.username=skycrateDB
spring.datasource.username=kshitij
spring.datasource.password=loa_dngLLA8729
spring.datasource.url=jdbc:mysql://192.168.29.36:3306/skycrate
spring.datasource.url=jdbc:mysql://192.168.29.55:3306/skycrate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
@@ -21,19 +19,24 @@ logging.level.org.springframework.security.config.annotation.authentication.conf
server.port=8080
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=changeit
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=mykey
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoints.enabled-by-default=true
# Allow unauthenticated access
#management.server.port=8080
#management.server.ssl.enabled=false
#management.endpoints.web.base-path=/actuator
#management.endpoints.web.exposure.include=*
# spring.application.name=skycrateBackend
# spring.servlet.multipart.max-file-size=1000MB
# spring.servlet.multipart.max-request-size=1000MB
# security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b
# security.jwt.expiration-time=3600000
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
# spring.datasource.username=root
# spring.datasource.password=sqlsys
# spring.datasource.url=jdbc:mysql://localhost:3306/skycrate
# spring.jpa.hibernate.ddl-auto=update
# spring.jpa.show-sql=true
# spring.jpa.properties.hibernate.format_sql=true
# server.port=8081