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 ### ### VS Code ###
.vscode/ .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
+80 -43
View File
@@ -1,57 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version> <version>3.4.4</version>
<relativePath/> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.skycrate.backend</groupId> <groupId>com.skycrate.backend</groupId>
<artifactId>skycrateBackend</artifactId> <artifactId>skycrateBackend</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name>skycrateBackend</name> <name>skycrateBackend</name>
<description>Cloud Storage App using HDFS</description> <description>Cloud Storage App using HDFS</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- Spring Boot Starters -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<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>
<!-- NEWLY ADDED DEPENDECIES-->
<!-- Spring Boot Web -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency> </dependency>
<!-- Spring Boot Security -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
<version>3.2.4</version>
</dependency> </dependency>
<!-- Spring Boot Data JPA -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> <version>3.2.4</version>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<!-- MySQL JDBC --> <!-- MySQL Driver -->
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version> <version>8.3.0</version>
</dependency> </dependency>
<!-- JWT --> <!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.2.4</version>
</dependency>
<!-- JSON Web Token (JWT) -->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId> <artifactId>jjwt-api</artifactId>
@@ -61,16 +93,14 @@
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId> <artifactId>jjwt-impl</artifactId>
<version>0.11.5</version> <version>0.11.5</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version> <version>0.11.5</version>
<scope>runtime</scope>
</dependency> </dependency>
<!-- Hadoop HDFS --> <!-- Hadoop Dependencies -->
<dependency> <dependency>
<groupId>org.apache.hadoop</groupId> <groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId> <artifactId>hadoop-common</artifactId>
@@ -82,72 +112,79 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.hadoop</groupId> <groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId> <artifactId>hadoop-hdfs</artifactId>
<version>3.4.1</version> <version>3.4.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.hadoop</groupId> <groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId> <artifactId>hadoop-client</artifactId>
<version>3.4.1</version> <version>3.4.1</version>
</dependency> </dependency>
<!-- Commons IO --> <!-- 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> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.11.0</version> <version>2.11.0</version>
</dependency> </dependency>
<!-- Caffeine Cache -->
<!-- NEWLY ADDED DEPENDENCY FOR DOWNLOAD ENDPOINT-->
<dependency> <dependency>
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>commons-io</groupId>
<artifactId>caffeine</artifactId> <artifactId>commons-io</artifactId>
<version>3.1.8</version> <version>2.11.0</version>
</dependency> </dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<!-- Compiler Plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration> <configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.30</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
<!-- Spring Boot Plugin -->
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
@@ -1,5 +1,7 @@
package com.skycrate.backend.skycrateBackend.config; package com.skycrate.backend.skycrateBackend.config;
import java.security.AuthProvider;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager; 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.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; 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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.skycrate.backend.skycrateBackend.repository.UserRepository; import com.skycrate.backend.skycrateBackend.repository.UserRepository;
// Application-wide security configuration.
// Configures user authentication, password encoding, and authentication provider.
@Configuration @Configuration
public class ApplicationConfiguration { public class ApplicationConfiguration {
private final UserRepository userRepository; private final UserRepository userRepository;
public ApplicationConfiguration(UserRepository userRepository){ public ApplicationConfiguration(UserRepository userRepository){
this.userRepository=userRepository; this.userRepository=userRepository;
}
// Custom UserDetailsService to fetch user details by email. }
@Bean @Bean
public UserDetailsService userDetailsService() { UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username) return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username)); .orElseThrow(() -> new UsernameNotFoundException("User not found"));
} }
// BCrypt password encoder with a higher strength for better security.
// Cost factor 12 is considered a good balance for production use.
@Bean @Bean
public PasswordEncoder passwordEncoder() { BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(12); return new BCryptPasswordEncoder();
} }
// 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 @Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{
return config.getAuthenticationManager(); 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; 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.fs.FileSystem;
import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -8,32 +8,13 @@ import org.springframework.context.annotation.Bean;
import java.net.URI; import java.net.URI;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
@org.springframework.context.annotation.Configuration
public class HDFSConfig { 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 { 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(); Configuration conf = new Configuration();
conf.set("fs.defaultFS", HDFS_URI); conf.set("fs.defaultFS", "hdfs://192.168.29.56:9000");
return FileSystem.get(new URI("hdfs://192.168.29.56:9000"), conf);
}
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);
}
}
} }
@@ -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 // import org.springframework.context.annotation.Bean;
public class SecurityConfig { // 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; // @Configuration
private final JwtAuthenticationFilter jwtAuthenticationFilter; // public class SecurityConfig {
public SecurityConfig(AuthenticationProvider authenticationProvider, // @Bean
JwtAuthenticationFilter jwtAuthenticationFilter) { // public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
this.authenticationProvider = authenticationProvider; // http
this.jwtAuthenticationFilter = jwtAuthenticationFilter; // .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 // return http.build();
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();
}
}
@@ -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; package com.skycrate.backend.skycrateBackend.controller;
import com.skycrate.backend.skycrateBackend.dto.LoginRequest; import org.springframework.web.bind.annotation.RestController;
import com.skycrate.backend.skycrateBackend.dto.LoginResponse;
import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto; import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshRequest; import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.dto.TokenRefreshResponse; import com.skycrate.backend.skycrateBackend.responses.LoginResponse;
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.services.AuthenticationService; import com.skycrate.backend.skycrateBackend.services.AuthenticationService;
import com.skycrate.backend.skycrateBackend.services.JwtService; 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.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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 @RestController
@RequestMapping("/api/auth")
public class AuthController { public class AuthController {
private final AuthenticationManager authManager;
private final JwtService jwtService; private final JwtService jwtService;
private final UserRepository userRepository; private AuthenticationService authenticationService;
private final RefreshTokenService refreshTokenService;
private final TokenBlacklistService tokenBlacklistService;
private final RateLimiterService rateLimiterService;
private final AuthenticationService authenticationService;
public AuthController( public AuthController(JwtService jwtService,AuthenticationService authenticationService){
AuthenticationManager authManager,
JwtService jwtService,
UserRepository userRepository,
RefreshTokenService refreshTokenService,
TokenBlacklistService tokenBlacklistService,
RateLimiterService rateLimiterService,
AuthenticationService authenticationService
) {
this.authManager = authManager;
this.jwtService=jwtService; this.jwtService=jwtService;
this.userRepository = userRepository;
this.refreshTokenService = refreshTokenService;
this.tokenBlacklistService = tokenBlacklistService;
this.rateLimiterService = rateLimiterService;
this.authenticationService=authenticationService; this.authenticationService=authenticationService;
} }
// New Register Endpoint @GetMapping("/test")
@PostMapping("/register") public String teString(@RequestParam String param) {
public ResponseEntity<?> register(@RequestBody RegisterUserDto request) { return new String();
User user = authenticationService.signUp(request);
return ResponseEntity.ok("User registered successfully with username: " + user.getUsername());
} }
@PostMapping("/login") @PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest) { public ResponseEntity<LoginResponse> LoginController(@RequestBody LoginUserDto entity) {
String ip = servletRequest.getRemoteAddr();
if (rateLimiterService.isBlocked(ip)) { User authenticatedUser=authenticationService.authenticate(entity);
return ResponseEntity.status(429).body("Too many login attempts. Please try again later."); String jwtToken=jwtService.generateToken(authenticatedUser);
LoginResponse loginResponse=new LoginResponse().setToken(jwtToken).setExpiresIn(jwtService.getExpirtationTime());
return ResponseEntity.ok(loginResponse);
} }
try { @PostMapping("/signup")
authManager.authenticate( public ResponseEntity<User> register(@RequestBody RegisterUserDto entity) {
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()) User registeredUser=authenticationService.signUp(entity);
);
} catch (Exception ex) {
rateLimiterService.recordFailedAttempt(ip); return ResponseEntity.ok(registeredUser);
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()));
}
@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");
}
@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; 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.dto.ResponseDTO;
import com.skycrate.backend.skycrateBackend.entity.User; import com.skycrate.backend.skycrateBackend.services.EncryptionUtil;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
import com.skycrate.backend.skycrateBackend.services.HDFSOperations; import com.skycrate.backend.skycrateBackend.services.HDFSOperations;
import com.skycrate.backend.skycrateBackend.utils.KeyUtil; 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.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -20,7 +17,6 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.List; 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.FileSystemResource; // For FileSystemResource
import org.springframework.core.io.Resource; // For Resource import org.springframework.core.io.Resource; // For Resource
import org.springframework.http.HttpHeaders; // For HttpHeaders 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 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 @RestController
@RequestMapping("/api/hdfs") @RequestMapping("/api/hdfs")
public class HDFScontroller { public class HDFScontroller {
private final HDFSOperations hdfsOperations; private final HDFSOperations hdfsOperations;
@Autowired
private UserRepository userRepository;
@Autowired @Autowired
public HDFScontroller(HDFSOperations hdfsOperations) { public HDFScontroller(HDFSOperations hdfsOperations) {
this.hdfsOperations = hdfsOperations; this.hdfsOperations = hdfsOperations;
@@ -61,7 +51,29 @@ public class HDFScontroller {
return new ResponseDTO("Failed to create folder: " + e.getMessage(), false); 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 {
// // 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") @PostMapping("/uploadFile")
public ResponseDTO uploadFile( public ResponseDTO uploadFile(
@RequestParam("file") MultipartFile file, @RequestParam("file") MultipartFile file,
@@ -69,178 +81,77 @@ public class HDFScontroller {
@RequestParam String uploadedFileName, @RequestParam String uploadedFileName,
@RequestParam String username) { @RequestParam String username) {
try { try {
// Retrieve the user from the database using the username // Upload file directly to HDFS without saving locally
User user = userRepository.findByUsername(username).orElseThrow(() -> new RuntimeException("User not found")); hdfsOperations.uploadFile(file, hdfsPath, uploadedFileName, username);
// 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); 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return new ResponseDTO("Failed to upload file to HDFS: " + e.getMessage(), false); 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"); // private String saveFileLocally(MultipartFile file) throws IOException {
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey); // // Create a temporary directory if it doesn't exist
byte[] fileData = file.getBytes(); // Path tmpDir = Paths.get("tmp");
byte[] encryptedData = aesCipher.doFinal(fileData); // 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 // @PostMapping("/downloadFile")
byte[] combined = new byte[4 + encryptedAesKey.length + encryptedData.length]; // public ResponseEntity<?> downloadFile(
combined[0] = (byte) (encryptedAesKey.length >> 24); // @RequestParam String hdfsPath,
combined[1] = (byte) (encryptedAesKey.length >> 16); // @RequestParam String username) {
combined[2] = (byte) (encryptedAesKey.length >> 8); // try {
combined[3] = (byte) encryptedAesKey.length; // // Define a temporary local path to download the file
// String localPath = "/app/tmp/downloaded/" + new File(hdfsPath).getName(); // Adjust the path as needed
System.arraycopy(encryptedAesKey, 0, combined, 4, encryptedAesKey.length); //
System.arraycopy(encryptedData, 0, combined, 4 + encryptedAesKey.length, encryptedData.length); // // Download the file from HDFS to the local path
// hdfsOperations.downloadFile(hdfsPath, localPath, username);
return combined; //
} // // Create a File object for the downloaded file
// File file = new File(localPath);
// Generate a random AES key // if (!file.exists()) {
private SecretKey generateAESKey() throws NoSuchAlgorithmException { // return ResponseEntity.status(HttpStatus.NOT_FOUND)
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); // .body(new ResponseDTO("File not found", false));
keyGen.init(256); // Use 256 bits for AES // }
return keyGen.generateKey(); //
} // // Create a Resource from the file
// Resource resource = new FileSystemResource(file);
private String saveFileLocally(MultipartFile file) throws IOException { // return ResponseEntity.ok()
// Create a temporary directory if it doesn't exist // .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
Path tmpDir = Paths.get("tmp"); // .body(resource);
if (!Files.exists(tmpDir)) { // } catch (Exception e) {
Files.createDirectories(tmpDir); // Create the directory if it doesn't exist // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
} // .body(new ResponseDTO("Failed to download file: " + e.getMessage(), false));
// }
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") @PostMapping("/downloadFile")
public ResponseEntity<Resource> downloadFile( public ResponseEntity<?> downloadFile(
@RequestParam String hdfsEncPath, @RequestParam String hdfsPath,
@RequestParam String username) { @RequestParam String username) {
try { try {
// Extract the file name and extension String fileName = new File(hdfsPath).getName();
String encFileName = new File(hdfsEncPath).getName(); InputStreamResource resource = hdfsOperations.downloadFile(hdfsPath, username);
String originalFileName = encFileName.replace(".enc", "");
String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
// 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() return ResponseEntity.ok()
.contentLength(decryptedFile.length()) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + decryptedFile.getName() + "\"")
.body(resource); .body(resource);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(null); .body(new ResponseDTO("Failed to download file: " + e.getMessage(), false));
}
}
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();
} }
} }
@@ -275,25 +186,4 @@ public class HDFScontroller {
.body("Failed to list files: " + e.getMessage()); .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; 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 { public class RegisterUserDto {
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email; private String email;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password; 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; private String firstname;
@NotBlank(message = "Last name is required")
private String lastname; private String lastname;
// Getters public String getEmail() {
public String getEmail() { return email; } return email;
public String getPassword() { return password; } }
public String getUsername() { return username; }
public String getFullname() { return fullname; } public RegisterUserDto setEmail(String email) {
public String getFirstname() { return firstname; } this.email = email;
public String getLastname() { return lastname; } return this;
}
// Setters
public void setEmail(String email) { this.email = email; } public String getPassword() {
public void setPassword(String password) { this.password = password; } return 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 RegisterUserDto setPassword(String password) {
public void setLastname(String lastname) { this.lastname = lastname; } 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 java.util.Optional;
import org.springframework.data.repository.CrudRepository; 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> { public interface UserRepository extends CrudRepository<User,Integer> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
// Custom query method to find user by username
Optional<User> findByUsername(String username);
/* /*
// might use later // might use later
Optional<User> findByVerificationCode(String verificationCode); 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,22 +1,14 @@
package com.skycrate.backend.skycrateBackend.services; 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.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.crypto.SecretKey; import com.skycrate.backend.skycrateBackend.dto.LoginUserDto;
import java.security.KeyPair; import com.skycrate.backend.skycrateBackend.dto.RegisterUserDto;
import java.security.NoSuchAlgorithmException; import com.skycrate.backend.skycrateBackend.models.User;
import com.skycrate.backend.skycrateBackend.repository.UserRepository;
@Service @Service
public class AuthenticationService { public class AuthenticationService {
@@ -25,69 +17,29 @@ public class AuthenticationService {
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
public AuthenticationService(UserRepository userRepository, public AuthenticationService( UserRepository userRepository, AuthenticationManager authenticationManager , PasswordEncoder passwordEncoder){
AuthenticationManager authenticationManager,
PasswordEncoder passwordEncoder) {
this.userRepository=userRepository; this.userRepository=userRepository;
this.passwordEncoder=passwordEncoder; this.passwordEncoder=passwordEncoder;
this.authenticationManager=authenticationManager; this.authenticationManager=authenticationManager;
} }
public User signUp(RegisterUserDto inputUser) { public User signUp(RegisterUserDto inputuser){
// Generate RSA key pair User user=new User(inputuser.getFirstname(),inputuser.getLastname(),inputuser.getEmail(),passwordEncoder.encode(inputuser.getPassword()));
KeyPair keyPair; /*
try { User user = new User()
keyPair = RSAKeyUtil.generateKeyPair(); .setFullname(inputuser.getFirstname(),inputuser.getLastname())
} catch (NoSuchAlgorithmException e) { .setEmail(inputuser.getEmail())
throw new RuntimeException("Failed to generate RSA key pair", e); .setPassword(passwordEncoder.encode(inputuser.getPassword()));
*/
return userRepository.save(user) ;
} }
// Encrypt private key using password-derived AES key public User authenticate(LoginUserDto inputuser){
byte[] salt = EncryptionUtil.generateSalt(); authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(inputuser.getEmail()
byte[] iv = EncryptionUtil.generateIv(); , inputuser.getPassword()));
byte[] encryptedPrivateKey; return userRepository.findByEmail(inputuser.getEmail()).orElseThrow();
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;
}
public User authenticate(LoginUserDto inputUser) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(inputUser.getEmail(), inputUser.getPassword())
);
return userRepository.findByEmail(inputUser.getEmail())
.orElseThrow(() -> new RuntimeException("User not found"));
}
} }
@@ -1,79 +1,85 @@
package com.skycrate.backend.skycrateBackend.services; package com.skycrate.backend.skycrateBackend.services;
import javax.crypto.*; import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*; import java.security.*;
import java.security.spec.InvalidKeySpecException; import java.util.Arrays;
import java.security.spec.KeySpec;
import java.util.Base64;
public class EncryptionUtil { 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 // Generate RSA Key Pair (Public & Private)
private static final int IV_LENGTH = 16; // for AES CBC public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
private static final int ITERATIONS = 65536; KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
private static final int KEY_LENGTH = 256; // bits keyGen.initialize(RSA_KEY_SIZE);
return keyGen.generateKeyPair();
// --- 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");
} }
// --- Encrypt data using AES-CBC --- // Encrypt data using AES (AES Key is encrypted using RSA)
public static byte[] encrypt(byte[] data, SecretKey key, byte[] iv) public static byte[] encrypt(byte[] data, PublicKey publicKey) throws Exception {
throws GeneralSecurityException { // 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); // Step 3: Encrypt the AES key with RSA
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); 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 --- // Decrypt data using RSA (AES Key is decrypted using RSA, then used for AES decryption)
public static byte[] decrypt(byte[] encryptedData, SecretKey key, byte[] iv) public static byte[] decrypt(byte[] encryptedCombined, PrivateKey privateKey) throws Exception {
throws GeneralSecurityException { // 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); System.arraycopy(encryptedCombined, 4, encryptedAesKey, 0, aesKeyLength);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); 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 --- // Generate a random AES key
public static byte[] generateSalt() { private static SecretKey generateAESKey() throws NoSuchAlgorithmException {
byte[] salt = new byte[SALT_LENGTH]; KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGORITHM);
new SecureRandom().nextBytes(salt); keyGen.init(AES_KEY_SIZE);
return salt; return keyGen.generateKey();
}
// --- 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);
} }
} }
@@ -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; package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.config.HDFSConfig; import com.skycrate.backend.skycrateBackend.config.HDFSConfig;
import com.skycrate.backend.skycrateBackend.entity.User; import com.skycrate.backend.skycrateBackend.utils.KeyUtil;
import com.skycrate.backend.skycrateBackend.repository.UserRepository; import org.apache.commons.io.IOUtils;
import com.skycrate.backend.skycrateBackend.utils.RSAKeyUtil; import org.apache.hadoop.fs.*;
import org.apache.hadoop.fs.FSDataOutputStream; import org.springframework.core.io.InputStreamResource;
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 org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
@@ -18,7 +14,6 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -32,129 +27,97 @@ import java.util.List;
@Service @Service
public class HDFSOperations { public class HDFSOperations {
private final UserRepository userRepository;
@Autowired // public void uploadFile(String localPath, String hdfsPath, String uploadedFileName, String username) {
public HDFSOperations(UserRepository userRepository) {
this.userRepository = userRepository;
}
// public void uploadFile(byte[] fileData, String hdfsPath, String uploadedFileName, String username) {
// try { // try {
// FileSystem fs = HDFSConfig.getHDFS(); // FileSystem fs = HDFSConfig.getHDFS();
// byte[] data = Files.readAllBytes(Paths.get(localPath)); // Read file as bytes
// //
// // Create an InputStream from the byte array // // Encrypt file (consider adding encryption here as needed)
// ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData); // 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; // 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 // Files.delete(Paths.get(tempFilePath));
// Path hdfsFilePath = new Path(finalHdfsPath);
// FSDataOutputStream outputStream = fs.create(hdfsFilePath);
// IOUtils.copyBytes(inputStream, outputStream, 4096, true);
//
// } catch (IOException e) { // } catch (IOException e) {
// // Handle I/O exception and log the error // // 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 (Exception e) {
// // Catch any other exceptions // // 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 { // try {
// FileSystem fs = HDFSConfig.getHDFS(); // FileSystem fs = HDFSConfig.getHDFS();
// String tempFilePath = localPath + ".enc";
// //
// // Extract file name and extension // fs.copyToLocalFile(new Path(hdfsPath), new Path(tempFilePath));
// String encFileName = new File(hdfsEncPath).getName();
// String originalFileName = encFileName.replace(".enc", "");
// String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
// //
// String fullDecryptedPath = localPathWithoutExt + "/" + originalFileName; // byte[] encryptedData = Files.readAllBytes(Paths.get(tempFilePath));
// String encFilePath = fullDecryptedPath + ".enc"; // byte[] decryptedData = encryptedData; // Decrypt if needed
// 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));
// //
// 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) { // } 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 { try {
FileSystem fs = HDFSConfig.getHDFS(); FileSystem fs = HDFSConfig.getHDFS();
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
String finalHdfsPath = hdfsPath.endsWith("/") ? hdfsPath + uploadedFileName : hdfsPath + "/" + uploadedFileName; // Read encrypted file directly from HDFS
Path hdfsFilePath = new Path(finalHdfsPath); FSDataInputStream hdfsInputStream = fs.open(new Path(hdfsPath));
try (FSDataOutputStream outputStream = fs.create(hdfsFilePath)) {
IOUtils.copyBytes(inputStream, outputStream, 4096, true); // 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) { } 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) { } 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) { public void createFolder(String hdfsPath) {
try { try {
@@ -1,69 +1,83 @@
package com.skycrate.backend.skycrateBackend.services; package com.skycrate.backend.skycrateBackend.services;
import com.skycrate.backend.skycrateBackend.entity.User; import java.security.Key;
import io.jsonwebtoken.*; import java.util.Date;
import io.jsonwebtoken.io.Decoders; import java.util.HashMap;
import io.jsonwebtoken.security.Keys; import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.security.Key; import io.jsonwebtoken.Claims;
import java.util.Date; import io.jsonwebtoken.Jwts;
import java.util.function.Function; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Service @Service
public class JwtService { public class JwtService {
@Value("${security.jwt.secret-key}") @Value("${security.jwt.secret-key}")
private String secretKey; private String secretKey;
@Value("${security.jwt.expiration-time}") @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){ public String extractUsername(String token){
return extractClaim(token,Claims::getSubject); return extractClaim(token,Claims::getSubject);
} }
public Date extractExpiration(String token) { public <T> T extractClaim(String token,Function<Claims,T> claimsResolver){
return extractClaim(token, Claims::getExpiration); final Claims claims=extractAllClaims(token);
return claimsResolver.apply(claims);
} }
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { public String generateToken(UserDetails userDetails) {
Claims claims = Jwts.parserBuilder() return generateToken(new HashMap<>(), userDetails);
.setSigningKey(getSigningKey()) }
.build()
.parseClaimsJws(token) public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
.getBody(); return buildToken(extraClaims, userDetails, jwtExpiration);
return claimsResolver.apply(claims); }
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) { public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token); final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
} }
public boolean isTokenExpired(String token) { private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date()); return extractExpiration(token).before(new Date());
} }
public String generateToken(UserDetails userDetails) { private Date extractExpiration(String token) {
return Jwts.builder() return extractClaim(token, Claims::getExpiration);
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
} }
public String generateToken(User user) { private Claims extractAllClaims(String token) {
return generateToken((UserDetails) user); 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.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; 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.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
public class KeyUtil { 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 { 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); byte[] bytes = Files.readAllBytes(path);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
return KeyFactory.getInstance("RSA").generatePrivate(keySpec); 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-file-size=1000MB
spring.servlet.multipart.max-request-size=1000MB spring.servlet.multipart.max-request-size=1000MB
spring.servlet.multipart.enabled=true security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7567c272e007b
security.jwt.secret-key=PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=
security.jwt.expiration-time=3600000 security.jwt.expiration-time=3600000
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.datasource.username=skycrateDB spring.datasource.username=kshitij
spring.datasource.password=loa_dngLLA8729 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.hibernate.ddl-auto=update
spring.jpa.show-sql=true spring.jpa.show-sql=true
@@ -21,19 +19,24 @@ logging.level.org.springframework.security.config.annotation.authentication.conf
server.port=8080 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 # spring.application.name=skycrateBackend
#management.server.ssl.enabled=false
#management.endpoints.web.base-path=/actuator # spring.servlet.multipart.max-file-size=1000MB
#management.endpoints.web.exposure.include=* # 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