From 535c0856e8d079d624c13aca8818d9e861b01bba Mon Sep 17 00:00:00 2001
From: KaNi-04 <136799210+KaNi-04@users.noreply.github.com>
Date: Mon, 5 Oct 2026 11:28:21 +0530
Subject: [PATCH] Secure API Initial Commit
---
pom.xml | 18 +
.../gov/secureapi/SecureApiApplication.java | 11 +
.../secureapi/config/DataSourceConfig.java | 84 +++
.../gov/secureapi/config/SecurityConfig.java | 77 ++
.../secureapi/controller/AuthController.java | 35 +
.../controller/CustomerController.java | 50 ++
.../controller/DBFOT_controller.java | 113 +++
.../secureapi/controller/MainController.java | 32 +
.../exception/GlobalExceptionHandler.java | 34 +
.../secureapi/filter/BearerTokenFilter.java | 81 ++
.../repository/CustomerRepository.java | 30 +
.../secureapi/repository/TokenRepository.java | 47 ++
.../secureapi/repository/UserRepository.java | 55 ++
.../gov/secureapi/service/DBFOT_service.java | 712 ++++++++++++++++++
.../gov/secureapi/service/DateTimeUtil.java | 34 +
.../gov/secureapi/service/TokenService.java | 97 +++
.../java/in/gov/secureapi/util/TokenUtil.java | 30 +
.../resources/application-local.properties | 3 +
.../application-production.properties | 3 +
src/main/resources/application.properties | 67 ++
src/main/resources/schema.sql | 0
21 files changed, 1613 insertions(+)
create mode 100644 pom.xml
create mode 100644 src/main/java/in/gov/secureapi/SecureApiApplication.java
create mode 100644 src/main/java/in/gov/secureapi/config/DataSourceConfig.java
create mode 100644 src/main/java/in/gov/secureapi/config/SecurityConfig.java
create mode 100644 src/main/java/in/gov/secureapi/controller/AuthController.java
create mode 100644 src/main/java/in/gov/secureapi/controller/CustomerController.java
create mode 100644 src/main/java/in/gov/secureapi/controller/DBFOT_controller.java
create mode 100644 src/main/java/in/gov/secureapi/controller/MainController.java
create mode 100644 src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java
create mode 100644 src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java
create mode 100644 src/main/java/in/gov/secureapi/repository/CustomerRepository.java
create mode 100644 src/main/java/in/gov/secureapi/repository/TokenRepository.java
create mode 100644 src/main/java/in/gov/secureapi/repository/UserRepository.java
create mode 100644 src/main/java/in/gov/secureapi/service/DBFOT_service.java
create mode 100644 src/main/java/in/gov/secureapi/service/DateTimeUtil.java
create mode 100644 src/main/java/in/gov/secureapi/service/TokenService.java
create mode 100644 src/main/java/in/gov/secureapi/util/TokenUtil.java
create mode 100644 src/main/resources/application-local.properties
create mode 100644 src/main/resources/application-production.properties
create mode 100644 src/main/resources/application.properties
create mode 100644 src/main/resources/schema.sql
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..18e2302
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ org.springframework.bootspring-boot-starter-parent3.5.6
+
+ in.gov.gccsecure-api1.0.0
+ 17
+
+ org.springframework.bootspring-boot-starter-web
+ org.springframework.bootspring-boot-starter-security
+ org.springframework.bootspring-boot-starter-jdbc
+ org.springframework.bootspring-boot-starter-data-jpa
+ org.springframework.bootspring-boot-starter-validation
+ com.mysqlmysql-connector-jruntime
+
+ org.springframework.bootspring-boot-maven-plugin
+
diff --git a/src/main/java/in/gov/secureapi/SecureApiApplication.java b/src/main/java/in/gov/secureapi/SecureApiApplication.java
new file mode 100644
index 0000000..1e720a0
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/SecureApiApplication.java
@@ -0,0 +1,11 @@
+package in.gov.secureapi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SecureApiApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SecureApiApplication.class, args);
+ }
+}
diff --git a/src/main/java/in/gov/secureapi/config/DataSourceConfig.java b/src/main/java/in/gov/secureapi/config/DataSourceConfig.java
new file mode 100644
index 0000000..f979d97
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/config/DataSourceConfig.java
@@ -0,0 +1,84 @@
+package in.gov.secureapi.config;
+
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.hibernate.jpa.HibernatePersistenceProvider;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@EnableTransactionManagement
+public class DataSourceConfig {
+
+ // private static String host = "localhost:3306";
+ // private static String dbpassword = "root";
+ // private static String dbpassword = "gccroot";
+
+ // AWS
+ private static String host =
+ "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
+ private static String dbpassword = "gcc-facial-password";
+
+ ////////////////////////////// (For GCC APP) ////////////////////////
+ @Configuration
+ @EnableTransactionManagement
+ @EnableJpaRepositories(entityManagerFactoryRef = "appEntityManagerFactory", transactionManagerRef = "appTransactionManager", basePackages = {
+ "in.gov.chennaicorporation" })
+
+ public static class MysqlAppDataSourceConfig {
+ @Primary
+ @Bean(name = "mysqlAppDataSource")
+ public DataSource mysqlAppDataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ dataSource.setUrl("jdbc:mysql://" + host + "/gcc_apps");
+ dataSource.setUsername("root");
+ dataSource.setPassword(dbpassword);
+ return dataSource;
+ }
+
+ @Primary
+ @Bean(name = "appEntityManagerFactory")
+ public LocalContainerEntityManagerFactoryBean entityManagerFactory(
+ @Qualifier("mysqlAppDataSource") DataSource dataSource) {
+ LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
+ entityManagerFactoryBean.setDataSource(dataSource);
+ entityManagerFactoryBean.setPackagesToScan("in.gov.chennaicorporation");
+ entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
+ Properties properties = new Properties();
+ properties.setProperty("hibernate.hbm2ddl.auto", "update");
+ entityManagerFactoryBean.setJpaProperties(properties);
+
+ return entityManagerFactoryBean;
+ }
+
+ @Primary
+ @Bean(name = "appTransactionManager")
+ public PlatformTransactionManager transactionManager(
+ @Qualifier("appEntityManagerFactory") jakarta.persistence.EntityManagerFactory entityManagerFactory) {
+ return new JpaTransactionManager(entityManagerFactory);
+ }
+ }
+
+ ////////// DBFOT_toilets //////////////////////
+ @Bean(name = "mysqldbfottoiletsGccDataSource")
+ public DataSource mysqldbfottoiletsGccDataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ dataSource.setUrl("jdbc:mysql://" + host + "/dbfot_toilets");
+ dataSource.setUsername("root");
+ dataSource.setPassword(dbpassword);
+ return dataSource;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/in/gov/secureapi/config/SecurityConfig.java b/src/main/java/in/gov/secureapi/config/SecurityConfig.java
new file mode 100644
index 0000000..27cde88
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/config/SecurityConfig.java
@@ -0,0 +1,77 @@
+package in.gov.secureapi.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.MediaType;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import in.gov.secureapi.filter.BearerTokenFilter;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Configuration
+public class SecurityConfig {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ SecurityFilterChain securityFilterChain(HttpSecurity http, BearerTokenFilter bearerTokenFilter) throws Exception {
+ http.csrf(csrf -> csrf.disable())
+ .formLogin(form -> form.disable())
+ .httpBasic(basic -> basic.disable())
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .exceptionHandling(ex -> ex
+ .authenticationEntryPoint((request, response, authException) -> {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding("UTF-8");
+
+ Map body = new LinkedHashMap<>();
+ body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+ body.put("status", false);
+ body.put("statusCode", HttpServletResponse.SC_UNAUTHORIZED);
+ body.put("error", "Unauthorized");
+ body.put("message", "Authentication required. Please provide a valid Bearer token in Authorization header.");
+ body.put("path", request.getRequestURI());
+
+ objectMapper.writeValue(response.getOutputStream(), body);
+ })
+ .accessDeniedHandler((request, response, accessDeniedException) -> {
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding("UTF-8");
+
+ Map body = new LinkedHashMap<>();
+ body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+ body.put("status", false);
+ body.put("statusCode", HttpServletResponse.SC_FORBIDDEN);
+ body.put("error", "Forbidden");
+ body.put("message", "Access denied: you do not have permission to access this resource.");
+ body.put("path", request.getRequestURI());
+
+ objectMapper.writeValue(response.getOutputStream(), body);
+ })
+ )
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/secure/api/auth/token", "/api/auth/token", "/error", "/actuator/health").permitAll()
+ .requestMatchers("/secure/**", "/api/**").authenticated()
+ .anyRequest().permitAll())
+ .addFilterBefore(bearerTokenFilter, UsernamePasswordAuthenticationFilter.class);
+ return http.build();
+ }
+}
diff --git a/src/main/java/in/gov/secureapi/controller/AuthController.java b/src/main/java/in/gov/secureapi/controller/AuthController.java
new file mode 100644
index 0000000..fece075
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/controller/AuthController.java
@@ -0,0 +1,35 @@
+package in.gov.secureapi.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import in.gov.secureapi.service.TokenService;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/secure/api/auth")
+public class AuthController {
+ private final TokenService tokenService;
+ public AuthController(TokenService tokenService) { this.tokenService = tokenService; }
+
+ @PostMapping("/token")
+ public ResponseEntity> createToken(@Valid @RequestBody TokenRequest request) {
+ var tokenResult = tokenService.authenticateAndCreate(request.username(), request.password());
+ return ResponseEntity.ok(Map.of("status", true, "message", "Token generated successfully", "data", tokenResult));
+ }
+
+ @PostMapping("/revoke")
+ public ResponseEntity> revoke(@RequestHeader(value="Authorization", required=false) String header) {
+ if (header == null || !header.startsWith("Bearer ")) return ResponseEntity.badRequest().body(Map.of("status",false,"message","Bearer token is required"));
+ tokenService.revoke(header.substring(7).trim());
+ return ResponseEntity.ok(Map.of("status",true,"message","Token revoked successfully"));
+ }
+
+ public record TokenRequest(
+ @NotBlank(message = "username is required") String username,
+ @NotBlank(message = "password is required") String password
+ ) {}
+}
diff --git a/src/main/java/in/gov/secureapi/controller/CustomerController.java b/src/main/java/in/gov/secureapi/controller/CustomerController.java
new file mode 100644
index 0000000..276eb57
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/controller/CustomerController.java
@@ -0,0 +1,50 @@
+package in.gov.secureapi.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import in.gov.secureapi.repository.CustomerRepository;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/customers")
+public class CustomerController {
+ private final CustomerRepository repo;
+ public CustomerController(CustomerRepository repo) { this.repo = repo; }
+
+ @PostMapping
+ public ResponseEntity> create(@Valid @RequestBody CustomerRequest req) {
+ long id = repo.insert(req.name(), req.mobile(), req.email(), req.address());
+ return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("status",true,"message","Customer created successfully","id",id));
+ }
+
+ @GetMapping
+ public ResponseEntity> all() { return ResponseEntity.ok(Map.of("status",true,"data",repo.findAll())); }
+
+ @GetMapping("/{id}")
+ public ResponseEntity> one(@PathVariable long id) {
+ return repo.findById(id).>map(x -> ResponseEntity.ok(Map.of("status",true,"data",x)))
+ .orElseGet(() -> ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found")));
+ }
+
+ @PutMapping("/{id}")
+ public ResponseEntity> update(@PathVariable long id, @Valid @RequestBody CustomerRequest req) {
+ int n = repo.update(id, req.name(), req.mobile(), req.email(), req.address());
+ if (n == 0) return ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found"));
+ return ResponseEntity.ok(Map.of("status",true,"message","Customer updated successfully"));
+ }
+
+ @DeleteMapping("/{id}")
+ public ResponseEntity> delete(@PathVariable long id) {
+ int n = repo.delete(id);
+ if (n == 0) return ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found"));
+ return ResponseEntity.ok(Map.of("status",true,"message","Customer deleted successfully"));
+ }
+
+ public record CustomerRequest(@NotBlank String name, String mobile, @Email String email, String address) {}
+}
diff --git a/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java b/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java
new file mode 100644
index 0000000..d76cd2e
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java
@@ -0,0 +1,113 @@
+package in.gov.secureapi.controller;
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import in.gov.secureapi.service.DBFOT_service;
+
+@RestController
+@RequestMapping("/secure/api/dbfotcontroller")
+public class DBFOT_controller {
+
+ @Autowired
+ private DBFOT_service dbfotService;
+
+ @PostMapping("/savecomplaints")
+ public List> savecomplaints(
+ @RequestParam(value = "zone", required = true) String zone,
+ @RequestParam(value = "ward", required = true) String ward,
+ @RequestParam(value = "location", required = true) String location,
+ @RequestParam(value = "status", required = true) String status,
+ @RequestParam(value = "address", required = true) String address,
+ @RequestParam(value = "source", required = true) String source,
+ @RequestParam(value = "pct", required = true) String pct,
+ @RequestParam(value = "complaint_id", required = true) String complaint_id,
+ @RequestParam(value = "description", required = true) String description,
+ @RequestParam(value = "turn_around_time", required = true) String turn_around_time,
+ @RequestParam(value = "vendorid", required = true) String vendorid
+ )
+ {
+
+ return dbfotService.savecomplaints(zone,ward,location,status,address,source,pct,complaint_id,description,turn_around_time,vendorid);
+
+ }
+
+ @PostMapping("/savecompshistory")
+ public List> savecomplaints_history(
+ @RequestParam(value = "complaint_id", required = true) String complaint_id,
+ @RequestParam(value = "status", required = true) String status,
+ @RequestParam(value = "complaint_raised_on", required = true) String complaint_raised_on,
+ @RequestParam(value = "complaint_raised_by", required = true) String complaint_raised_by,
+ @RequestParam(value = "complaint_updated_on", required = true) String complaint_updated_on,
+ @RequestParam(value = "complaint_updated_by", required = true) String complaint_updated_by,
+ @RequestParam(value = "remarks", required = true) String remarks,
+ @RequestParam(value = "imgfile", required = false) MultipartFile imgfile,
+ @RequestParam(value = "cby", required = true) String cby
+ )
+ {
+
+ return dbfotService.savecomplaints_history(complaint_id,status,complaint_raised_on,complaint_raised_by,complaint_updated_on,complaint_updated_by,remarks,imgfile,cby);
+
+ }
+
+ @PostMapping("/savemanpower")
+ public List> savemanpower(
+ @RequestParam(value = "zone", required = true) String zone,
+ @RequestParam(value = "ward", required = true) String ward,
+ @RequestParam(value = "category", required = true) String category,
+ @RequestParam(value = "pct", required = true) String pct,
+ @RequestParam(value = "status", required = true) String status,
+ @RequestParam(value = "vendorid", required = true) String vendorid
+ )
+ {
+
+ return dbfotService.savemanpower(zone,ward,category,pct,status,vendorid);
+
+ }
+
+ @PostMapping(value = "/savemanpoweremps", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public List> savemanpoweremps(
+ @RequestParam(value = "pct", required = true) String pct,
+ @RequestPart(value = "employee_details", required = true) String employee_details,
+ @RequestParam Map files
+ ){
+
+ return dbfotService.savemanpoweremps(pct,employee_details,files);
+ }
+
+ @PostMapping("/savepctmaster")
+ public List> savepctmaster(
+ @RequestParam(value="zone",required = true) String zone,
+ @RequestParam(value="ward",required = true) String ward,
+ @RequestParam(value="address",required = true) String address,
+ @RequestParam(value="category",required = true) String category,
+ @RequestParam(value="pct",required = true) String pct,
+ @RequestParam(value="status",required = true) String status,
+ @RequestParam(value="vendorid",required = true) String vendorid
+ ){
+
+ return dbfotService.savepctmaster(zone,ward,address,category,pct,status,vendorid);
+ }
+
+ @PostMapping("/updatepctstatus")
+ public List> updatepctstatus(
+ @RequestParam(value="pct",required = true) String pct,
+ @RequestParam(value="status",required = true) String status,
+ @RequestParam(value="vendorid",required = true) String vendorid
+ ){
+
+ return dbfotService.updatepctstatus(pct,status,vendorid);
+ }
+
+
+}
diff --git a/src/main/java/in/gov/secureapi/controller/MainController.java b/src/main/java/in/gov/secureapi/controller/MainController.java
new file mode 100644
index 0000000..e068579
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/controller/MainController.java
@@ -0,0 +1,32 @@
+package in.gov.secureapi.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/secure")
+public class MainController {
+
+ @GetMapping
+ public ResponseEntity> index(Authentication authentication) {
+ String username = (authentication != null && authentication.isAuthenticated())
+ ? authentication.getName()
+ : "Anonymous";
+
+ Map response = new LinkedHashMap<>();
+ response.put("status", true);
+ response.put("message", "Secure endpoint accessed successfully");
+ response.put("user", username);
+ response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java b/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..5bca121
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java
@@ -0,0 +1,34 @@
+package in.gov.secureapi.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import java.util.Map;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity> validation(MethodArgumentNotValidException ex) {
+ String message = ex.getBindingResult().getFieldErrors().stream()
+ .map(e -> e.getField() + ": " + e.getDefaultMessage()).findFirst().orElse("Validation failed");
+ return ResponseEntity.badRequest().body(Map.of("status", false, "message", message));
+ }
+
+ @ExceptionHandler({BadCredentialsException.class, DisabledException.class})
+ public ResponseEntity> authenticationError(Exception ex) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+ .body(Map.of("status", false, "message", ex.getMessage()));
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity> other(Exception ex) {
+ ex.printStackTrace();
+
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(Map.of("status", false, "message", "Internal server error: " + ex.getMessage()));
+ }
+}
diff --git a/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java b/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java
new file mode 100644
index 0000000..44b5951
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java
@@ -0,0 +1,81 @@
+package in.gov.secureapi.filter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import in.gov.secureapi.service.TokenService;
+import in.gov.secureapi.service.TokenService.TokenStatus;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Component
+public class BearerTokenFilter extends OncePerRequestFilter {
+ private final TokenService tokenService;
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public BearerTokenFilter(TokenService tokenService) {
+ this.tokenService = tokenService;
+ }
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) {
+ String path = request.getRequestURI();
+ return path.startsWith("/secure/api/auth/token") || path.startsWith("/api/auth/token") || path.startsWith("/error") || path.startsWith("/actuator");
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws ServletException, IOException {
+ String header = request.getHeader("Authorization");
+
+ if (header != null && header.startsWith("Bearer ")) {
+ String token = header.substring(7).trim();
+ if (!token.isBlank()) {
+ var validation = tokenService.validateDetailed(token);
+
+ if (validation.status() == TokenStatus.VALID) {
+ var auth = new UsernamePasswordAuthenticationToken(validation.info().username(), null, AuthorityUtils.NO_AUTHORITIES);
+ SecurityContextHolder.getContext().setAuthentication(auth);
+ } else {
+ sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized", validation.message(), request.getRequestURI());
+ return;
+ }
+ } else {
+ sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized", "Bearer token is empty", request.getRequestURI());
+ return;
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ private void sendErrorResponse(HttpServletResponse response, int statusCode, String error, String message, String path)
+ throws IOException {
+ response.setStatus(statusCode);
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setCharacterEncoding("UTF-8");
+
+ Map body = new LinkedHashMap<>();
+ body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+ body.put("status", false);
+ body.put("statusCode", statusCode);
+ body.put("error", error);
+ body.put("message", message);
+ body.put("path", path);
+
+ objectMapper.writeValue(response.getOutputStream(), body);
+ }
+}
diff --git a/src/main/java/in/gov/secureapi/repository/CustomerRepository.java b/src/main/java/in/gov/secureapi/repository/CustomerRepository.java
new file mode 100644
index 0000000..fad08be
--- /dev/null
+++ b/src/main/java/in/gov/secureapi/repository/CustomerRepository.java
@@ -0,0 +1,30 @@
+package in.gov.secureapi.repository;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+@Repository
+public class CustomerRepository {
+ private final JdbcTemplate jdbc;
+ public CustomerRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; }
+
+ public long insert(String name, String mobile, String email, String address) {
+ jdbc.update("INSERT INTO customers(name,mobile,email,address) VALUES (?,?,?,?)", name, mobile, email, address);
+ Long id = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+ return id;
+ }
+ public List