Browse Source

Secure API Initial Commit

master
KaNi-04 1 week ago
commit
535c0856e8
  1. 18
      pom.xml
  2. 11
      src/main/java/in/gov/secureapi/SecureApiApplication.java
  3. 84
      src/main/java/in/gov/secureapi/config/DataSourceConfig.java
  4. 77
      src/main/java/in/gov/secureapi/config/SecurityConfig.java
  5. 35
      src/main/java/in/gov/secureapi/controller/AuthController.java
  6. 50
      src/main/java/in/gov/secureapi/controller/CustomerController.java
  7. 113
      src/main/java/in/gov/secureapi/controller/DBFOT_controller.java
  8. 32
      src/main/java/in/gov/secureapi/controller/MainController.java
  9. 34
      src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java
  10. 81
      src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java
  11. 30
      src/main/java/in/gov/secureapi/repository/CustomerRepository.java
  12. 47
      src/main/java/in/gov/secureapi/repository/TokenRepository.java
  13. 55
      src/main/java/in/gov/secureapi/repository/UserRepository.java
  14. 712
      src/main/java/in/gov/secureapi/service/DBFOT_service.java
  15. 34
      src/main/java/in/gov/secureapi/service/DateTimeUtil.java
  16. 97
      src/main/java/in/gov/secureapi/service/TokenService.java
  17. 30
      src/main/java/in/gov/secureapi/util/TokenUtil.java
  18. 3
      src/main/resources/application-local.properties
  19. 3
      src/main/resources/application-production.properties
  20. 67
      src/main/resources/application.properties
  21. 0
      src/main/resources/schema.sql

18
pom.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.6</version><relativePath/>
</parent>
<groupId>in.gov.gcc</groupId><artifactId>secure-api</artifactId><version>1.0.0</version>
<properties><java.version>17</java.version></properties>
<dependencies>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>
</dependencies>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

11
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);
}
}

84
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;
}
}

77
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<String, Object> 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<String, Object> 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();
}
}

35
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
) {}
}

50
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).<ResponseEntity<?>>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) {}
}

113
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<String, MultipartFile> 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);
}
}

32
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<String, Object> 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);
}
}

34
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()));
}
}

81
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<String, Object> 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);
}
}

30
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<Map<String,Object>> findAll() {
return jdbc.queryForList("SELECT id,name,mobile,email,address,created_at,updated_at FROM customers ORDER BY id DESC");
}
public Optional<Map<String,Object>> findById(long id) {
var list = jdbc.queryForList("SELECT id,name,mobile,email,address,created_at,updated_at FROM customers WHERE id=?", id);
return list.stream().findFirst();
}
public int update(long id, String name, String mobile, String email, String address) {
return jdbc.update("UPDATE customers SET name=?,mobile=?,email=?,address=? WHERE id=?", name,mobile,email,address,id);
}
public int delete(long id) { return jdbc.update("DELETE FROM customers WHERE id=?", id); }
}

47
src/main/java/in/gov/secureapi/repository/TokenRepository.java

@ -0,0 +1,47 @@
package in.gov.secureapi.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Optional;
import javax.sql.DataSource;
@Repository
public class TokenRepository {
private JdbcTemplate jdbc;
@Autowired
public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource DBFOTTOILETSSource) {
this.jdbc = new JdbcTemplate(DBFOTTOILETSSource);
}
public void save(String username, String tokenHash, LocalDateTime expiresAt) {
// Step 1: Deactivate any previous active tokens for this client
jdbc.update("UPDATE api_users SET active = FALSE WHERE username = ? AND active = TRUE", username);
// Step 2: Insert the newly generated active token
jdbc.update("INSERT INTO api_users(username, token_hash, expires_at, active) VALUES (?, ?, ?, TRUE)",
username, tokenHash, Timestamp.valueOf(expiresAt));
}
public Optional<TokenInfo> findByTokenHash(String tokenHash) {
var list = jdbc.query("SELECT username, expires_at, active FROM api_users WHERE token_hash=? ORDER BY id DESC LIMIT 1",
(rs, row) -> new TokenInfo(rs.getString("username"), rs.getTimestamp("expires_at").toLocalDateTime(), rs.getBoolean("active")), tokenHash);
return list.stream().findFirst();
}
public Optional<TokenInfo> findValid(String tokenHash) {
return findByTokenHash(tokenHash)
.filter(info -> info.active() && info.expiresAt().isAfter(LocalDateTime.now()));
}
public void revoke(String tokenHash) {
jdbc.update("UPDATE api_users SET active=FALSE WHERE token_hash=?", tokenHash);
}
public record TokenInfo(String username, LocalDateTime expiresAt, boolean active) {}
}

55
src/main/java/in/gov/secureapi/repository/UserRepository.java

@ -0,0 +1,55 @@
package in.gov.secureapi.repository;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.util.Optional;
@Repository
public class UserRepository {
private JdbcTemplate jdbc;
@Autowired
public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource dbfotToiletsSource) {
this.jdbc = new JdbcTemplate(dbfotToiletsSource);
}
@PostConstruct
public void init() {
try {
jdbc.execute("""
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
""");
} catch (Exception e) {
// Log but don't fail if table or permissions already established
}
}
public Optional<UserRecord> findByUsername(String username) {
var list = jdbc.query(
"SELECT id, username, password, active FROM users WHERE username = ? LIMIT 1",
(rs, rowNum) -> new UserRecord(
rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getBoolean("active")
),
username
);
return list.stream().findFirst();
}
public record UserRecord(long id, String username, String password, boolean active) {}
}

712
src/main/java/in/gov/secureapi/service/DBFOT_service.java

@ -0,0 +1,712 @@
package in.gov.secureapi.service;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class DBFOT_service {
private JdbcTemplate jdbcTemplate;
private final Environment environment;
private String fileBaseUrl;
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final int STRING_LENGTH = 15;
private static final Random RANDOM = new SecureRandom();
@Autowired
public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource DBFOTTOILETSSource) {
this.jdbcTemplate = new JdbcTemplate(DBFOTTOILETSSource);
}
@Autowired
public DBFOT_service(Environment environment) {
this.environment = environment;
this.fileBaseUrl = environment.getProperty("fileBaseUrl");
}
public static String generateRandomString() {
StringBuilder result = new StringBuilder(STRING_LENGTH);
for (int i = 0; i < STRING_LENGTH; i++) {
result.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length())));
}
return result.toString();
}
public static String generateRandomFileString(int lenthval) {
StringBuilder result = new StringBuilder(lenthval);
for (int i = 0; i < lenthval; i++) {
result.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length())));
}
return result.toString();
}
private byte[] compressImage(BufferedImage image, float quality) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(byteArrayOutputStream);
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
writer.setOutput(imageOutputStream);
ImageWriteParam param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
}
writer.write(null, new javax.imageio.IIOImage(image, null, null), param);
writer.dispose();
imageOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
public String fileUpload(MultipartFile file, String name) {
// Set the file path where you want to save it
String uploadDirectory = environment.getProperty("file.upload.directory");
String serviceFolderName = environment.getProperty("dbfot_toilets");
var year = DateTimeUtil.getCurrentYear();
var month = DateTimeUtil.getCurrentMonth();
uploadDirectory = uploadDirectory + serviceFolderName + year + "/" + month;
try {
// Create directory if it doesn't exist
Path directoryPath = Paths.get(uploadDirectory);
if (!Files.exists(directoryPath)) {
Files.createDirectories(directoryPath);
}
// Datetime string
String datetimetxt = DateTimeUtil.getCurrentDateTime();
// File name
String fileName = name + "_" + datetimetxt + "_" + file.getOriginalFilename();
fileName = fileName.replaceAll("\\s+", ""); // Remove space on filename
String filePath = uploadDirectory + "/" + fileName;
String filepath_txt = "/" + serviceFolderName + year + "/" + month + "/" + fileName;
// Create a new Path object
Path path = Paths.get(filePath);
// Get the bytes of the file
byte[] bytes = file.getBytes();
String originalFileName = file.getOriginalFilename();
String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1).toLowerCase();
if ("jpg".equals(extension) || "jpeg".equals(extension)) {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
byte[] compressedBytes = compressImage(image, 0.5f); // Compress with 50% quality
// Write the compressed bytes to the file
Files.write(path, compressedBytes);
} else {
// Write the original bytes to the file
Files.write(path, bytes);
}
//System.out.println(filePath);
return filepath_txt;
} catch (IOException e) {
e.printStackTrace();
return "Failed to save file " + file.getOriginalFilename();
}
}
public List<?> savecomplaints(String zone, String ward, String location, String status, String address,
String source, String pct, String complaint_id, String description, String turn_around_time,
String vendorid) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT id, zone, ward, location, status, address, source, pct, " +
"complaint_id, description, turn_around_time, vendorid, " +
"created_date, isactive, isdelete " +
"FROM complaints_details " +
"WHERE complaint_id = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 ";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, complaint_id);
if (complaintList.isEmpty()) {
String saveSql = "INSERT INTO complaints_details (zone,ward,location,status,address,source,pct,complaint_id,description,turn_around_time,vendorid) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
int affectedRows = jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, zone);
ps.setString(2, ward);
ps.setString(3, location);
ps.setString(4, status);
ps.setString(5, address);
ps.setString(6, source);
ps.setString(7, pct);
ps.setString(8, complaint_id);
ps.setString(9, description);
ps.setString(10, turn_around_time);
ps.setString(11, vendorid);
return ps;
}, keyHolder);
if (affectedRows > 0) {
response.put("status", "Success");
response.put("message", "Complaints Saved Successfully");
} else {
response.put("status", "Failed");
response.put("message", "Error while saving complaints");
}
}
else {
response.put("status", "Failed");
response.put("message",
"Duplicate Complaint ID " + complaint_id +
" already found in complaints_details");
return Collections.singletonList(response);
}
} catch (Exception e) {
e.printStackTrace();
response.put("status", "Error");
response.put("message", "Error while saving complaints");
}
return Collections.singletonList(response);
}
@Transactional
public List<?> savecomplaints_history(String complaint_id, String status, String complaint_raised_on,
String complaint_raised_by, String complaint_updated_on, String complaint_updated_by, String remarks,
MultipartFile imgfile, String cby) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT id, zone, ward, location, status, address, source, pct, " +
"complaint_id, description, turn_around_time, vendorid, " +
"created_date, isactive, isdelete " +
"FROM complaints_details " +
"WHERE complaint_id = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 " +
"FOR UPDATE";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, complaint_id);
if (complaintList.isEmpty()) {
response.put("status", "Failed");
response.put("message",
"Complaint ID " + complaint_id +
" not found in complaints_details");
return Collections.singletonList(response);
}
Map<String, Object> complaint = complaintList.get(0);
String imgFileValue = null;
if (imgfile != null && !imgfile.isEmpty()) {
imgFileValue = fileUpload(imgfile, "dbfot_toilet_img");
}
String historySql =
"INSERT INTO complaints_history " +
"(complaint_id, status, complaint_raised_on, complaint_raised_by, " +
"complaint_updated_on, complaint_updated_by, remarks, imgfile, cby) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
int historyAffectedRows = jdbcTemplate.update(
historySql,
complaint_id,
status,
complaint_raised_on,
complaint_raised_by,
complaint_updated_on,
complaint_updated_by,
remarks,
imgFileValue,
cby
);
if (historyAffectedRows <= 0) {
response.put("status", "Failed");
response.put("message","Unable to save complaint history");
return Collections.singletonList(response);
}
String logSql =
"INSERT INTO complaints_details_log " +
"(cd_id, zone, ward, location, status, address, source, pct, " +
"complaint_id, description, turn_around_time, vendorid, " +
"created_date, isactive, isdelete) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
int logAffectedRows = jdbcTemplate.update(
logSql,
complaint.get("id"),
complaint.get("zone"),
complaint.get("ward"),
complaint.get("location"),
complaint.get("status"),
complaint.get("address"),
complaint.get("source"),
complaint.get("pct"),
complaint.get("complaint_id"),
complaint.get("description"),
complaint.get("turn_around_time"),
complaint.get("vendorid"),
complaint.get("created_date"),
complaint.get("isactive"),
complaint.get("isdelete")
);
if (logAffectedRows > 0) {
String updateSql =
"UPDATE complaints_details " +
"SET status = ? " +
"WHERE complaint_id = ? " +
"AND isactive = 1 " +
"AND isdelete = 0";
int updateAffectedRows = jdbcTemplate.update(
updateSql,
status,
complaint_id
);
if (updateAffectedRows > 0) {
response.put("status", "Success");
response.put("message",
"Complaint history saved and complaint status updated successfully");
} else {
/*
* Throw exception so @Transactional rolls everything back.
*/
throw new RuntimeException(
"Unable to update complaint status"
);
}
} else {
/*
* Throw exception so history insert is also rolled back.
*/
throw new RuntimeException(
"Unable to save complaint details log"
);
}
} catch (Exception e) {
e.printStackTrace();
response.put("status", "Error");
response.put("message", "Error while saving complaints history");
}
return Collections.singletonList(response);
}
public List<?> savemanpower(String zone, String ward, String category, String pct, String status,String vendorid) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT * " +
"FROM man_power_details " +
"WHERE pct = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 ";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, pct);
if (complaintList.isEmpty()) {
String saveSql = "INSERT INTO man_power_details (zone,ward,category,pct,status,vendorid) " +
"VALUES (?,?,?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
int affectedRows = jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, zone);
ps.setString(2, ward);
ps.setString(3, category);
ps.setString(4, pct);
ps.setString(5, status);
ps.setString(6, vendorid);
return ps;
}, keyHolder);
if (affectedRows > 0) {
response.put("status", "Success");
response.put("message", "Manpower details Saved Successfully");
} else {
response.put("status", "Failed");
response.put("message", "Error while saving man power details");
}
}
else {
response.put("status", "Failed");
response.put("message","Duplicate PCT " + pct +" already found in man_power_details");
return Collections.singletonList(response);
}
} catch (Exception e) {
e.printStackTrace();
response.put("status", "Error");
response.put("message", "Error while saving manpower details");
}
return Collections.singletonList(response);
}
@Transactional
public List<?> savemanpoweremps(String pct, String employee_details,Map<String, MultipartFile> files) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT * " +
"FROM man_power_details " +
"WHERE pct = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 ";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, pct);
if (complaintList.isEmpty()) {
response.put("status", "Failed");
response.put("message","PCT " + pct +" not found in man_power_details");
return Collections.singletonList(response);
}
else {
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> employeeList =
objectMapper.readValue(
employee_details,
new TypeReference<List<Map<String, Object>>>() {}
);
if (employeeList == null || employeeList.isEmpty()) {
response.put("status", "Failed");
response.put("message", "Manpower Employee details cannot be empty");
return Collections.singletonList(response);
}
String saveSql =
"INSERT INTO man_power_employees " +
"(pct, emp_name, att_date, location, mobile_number, imgfile) " +
"VALUES (?, ?, ?, ?, ?, ?)";
int totalSaved = 0;
for (Map<String, Object> employee : employeeList) {
String empName =employee.get("emp_name") != null ? employee.get("emp_name").toString(): null;
String attDate =employee.get("att_date") != null ? employee.get("att_date").toString() : null;
String location =employee.get("location") != null ? employee.get("location").toString(): null;
String mobileNumber =employee.get("mobile_number") != null ? employee.get("mobile_number").toString() : null;
String fileKey =employee.get("fileKey") != null ? employee.get("fileKey").toString() : null;
MultipartFile file = null;
String filePath = null;
if (fileKey != null && !fileKey.trim().isEmpty()) {
file = files.get(fileKey);
if (file != null && !file.isEmpty()) {
filePath = fileUpload(file, "dbfot_manpower_img");
}
}
int affectedRows = jdbcTemplate.update(saveSql,pct,empName,attDate,location,mobileNumber, filePath
);
if (affectedRows > 0) {
totalSaved++;
} else {
throw new RuntimeException("Failed to save employee: " + empName);
}
}
response.put("status", "Success");
response.put("message","Man power employees saved successfully");
response.put("count", totalSaved);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error while saving man power employees", e);
}
return Collections.singletonList(response);
}
public List<?> savepctmaster(String zone, String ward, String address, String category, String pct,
String status, String vendorid) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT * " +
"FROM pct_master " +
"WHERE pct = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 ";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, pct);
if (complaintList.isEmpty()) {
String saveSql = "INSERT INTO pct_master (zone,ward,address,category,pct,status,vendorid) " +
"VALUES (?,?,?,?,?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
int affectedRows = jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, zone);
ps.setString(2, ward);
ps.setString(3, address);
ps.setString(4, category);
ps.setString(5, pct);
ps.setString(6, status);
ps.setString(7, vendorid);
return ps;
}, keyHolder);
if (affectedRows > 0) {
response.put("status", "Success");
response.put("message","PCT Master Saved Successfully");
} else {
response.put("status", "Failed");
response.put("message", "Error while saving pct master");
}
}
else {
response.put("status", "Failed");
response.put("message","Duplicate PCT " + pct +" already found in pct master");
return Collections.singletonList(response);
}
} catch (Exception e) {
e.printStackTrace();
response.put("status", "Error");
response.put("message", "Error while saving pct master");
}
return Collections.singletonList(response);
}
public List<?> updatepctstatus(String pct, String status, String vendorid) {
Map<String, Object> response = new HashMap<>();
try {
String checkSql =
"SELECT id, zone, ward,address,category,pct,status, " +
"vendorid,created_date, isactive, isdelete " +
"FROM pct_master " +
"WHERE pct = ? " +
"AND isactive = 1 " +
"AND isdelete = 0 " +
"FOR UPDATE";
List<Map<String, Object>> complaintList =
jdbcTemplate.queryForList(checkSql, pct);
if (complaintList.isEmpty()) {
response.put("status", "Failed");
response.put("message","PCT " + pct + " not found in pct_master");
return Collections.singletonList(response);
}
Map<String, Object> complaint = complaintList.get(0);
String logSql =
"INSERT INTO pct_master_log " +
"(pct_master_id, zone, ward, address,category,pct, status, " +
"vendorid,created_date, isactive, isdelete) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
int logAffectedRows = jdbcTemplate.update(
logSql,
complaint.get("id"),
complaint.get("zone"),
complaint.get("ward"),
complaint.get("address"),
complaint.get("category"),
complaint.get("pct"),
complaint.get("status"),
complaint.get("vendorid"),
complaint.get("created_date"),
complaint.get("isactive"),
complaint.get("isdelete")
);
if (logAffectedRows > 0) {
String updateSql =
"UPDATE pct_master " +
"SET status = ? " +
"WHERE pct = ? " +
"AND isactive = 1 " +
"AND isdelete = 0";
int updateAffectedRows = jdbcTemplate.update(
updateSql,
status,
pct
);
if (updateAffectedRows > 0) {
response.put("status", "Success");
response.put("message","PCT status updated successfully");
} else {
/*
* Throw exception so @Transactional rolls everything back.
*/
throw new RuntimeException(
"Unable to update pct status"
);
}
} else {
/*
* Throw exception so history insert is also rolled back.
*/
throw new RuntimeException(
"Unable to save in pct master log"
);
}
} catch (Exception e) {
e.printStackTrace();
response.put("status", "Error");
response.put("message", "Error while updating pct master");
}
return Collections.singletonList(response);
}
}

34
src/main/java/in/gov/secureapi/service/DateTimeUtil.java

@ -0,0 +1,34 @@
package in.gov.secureapi.service;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
public class DateTimeUtil {
public static String getCurrentDateTime() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddMMyyyyHHmmss");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
public static String getCurrentYear() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
public static String getCurrentMonth() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
public static String getCurrentDay() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
public static String getCurrentDateTimeMysql() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
}

97
src/main/java/in/gov/secureapi/service/TokenService.java

@ -0,0 +1,97 @@
package in.gov.secureapi.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import in.gov.secureapi.repository.TokenRepository;
import in.gov.secureapi.repository.UserRepository;
import in.gov.secureapi.util.TokenUtil;
import java.time.LocalDateTime;
@Service
public class TokenService {
private final TokenRepository repo;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final long ttlHours;
public TokenService(
TokenRepository repo,
UserRepository userRepository,
PasswordEncoder passwordEncoder,
@Value("${api.token.ttl-hours:24}") long ttlHours) {
this.repo = repo;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.ttlHours = ttlHours;
}
public TokenResult authenticateAndCreate(String username, String rawPassword) {
var userOpt = userRepository.findByUsername(username);
if (userOpt.isEmpty()) {
throw new BadCredentialsException("Invalid username or password");
}
var user = userOpt.get();
if (!user.active()) {
throw new DisabledException("User account is inactive");
}
boolean matches = passwordEncoder.matches(rawPassword, user.password());
if (!matches && rawPassword.equals(user.password())) {
matches = true;
}
if (!matches) {
throw new BadCredentialsException("Invalid username or password");
}
return create(user.username());
}
public TokenResult create(String username) {
String token = TokenUtil.generateToken();
LocalDateTime expires = LocalDateTime.now().plusHours(ttlHours);
repo.save(username, TokenUtil.sha256(token), expires);
return new TokenResult(token, username, expires);
}
public enum TokenStatus {
VALID,
EXPIRED,
INACTIVE,
INVALID
}
public record TokenValidation(TokenStatus status, TokenRepository.TokenInfo info, String message) {}
public TokenValidation validateDetailed(String token) {
if (token == null || token.isBlank()) {
return new TokenValidation(TokenStatus.INVALID, null, "Bearer token is missing or empty");
}
String tokenHash = TokenUtil.sha256(token);
var tokenOpt = repo.findByTokenHash(tokenHash);
if (tokenOpt.isEmpty()) {
return new TokenValidation(TokenStatus.INVALID, null, "Invalid token: token not found");
}
var info = tokenOpt.get();
if (!info.active()) {
return new TokenValidation(TokenStatus.INACTIVE, info, "Token is inactive or has been revoked. Please authenticate again.");
}
if (!info.expiresAt().isAfter(LocalDateTime.now())) {
return new TokenValidation(TokenStatus.EXPIRED, info, "Token has expired. Please authenticate again to generate a new token.");
}
return new TokenValidation(TokenStatus.VALID, info, "Token is valid");
}
public TokenRepository.TokenInfo validate(String token) {
var validation = validateDetailed(token);
return validation.status() == TokenStatus.VALID ? validation.info() : null;
}
public void revoke(String token) { repo.revoke(TokenUtil.sha256(token)); }
public record TokenResult(String token, String username, LocalDateTime expiresAt) {}
}

30
src/main/java/in/gov/secureapi/util/TokenUtil.java

@ -0,0 +1,30 @@
package in.gov.secureapi.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public final class TokenUtil {
private static final SecureRandom RANDOM = new SecureRandom();
private TokenUtil() {}
public static String generateToken() {
byte[] bytes = new byte[32];
RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
public static String sha256(String value) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(64);
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 unavailable", e);
}
}
}

3
src/main/resources/application-local.properties

@ -0,0 +1,3 @@
assets.base-url=/assets/
fileBaseUrl=http://localhost:8088

3
src/main/resources/application-production.properties

@ -0,0 +1,3 @@
assets.base-url=/gccofficialapp/assets/
fileBaseUrl=https://gccservices.in

67
src/main/resources/application.properties

@ -0,0 +1,67 @@
# spring.profiles.active=local
spring.profiles.active=production
spring.application.name=
# Enable live reload (default is true)
spring.devtools.livereload.enabled=true
# Enable restart (default is true)
spring.devtools.restart.enabled=true
# HTTP Server
server.port = 8088
server.error.include-stacktrace= on_param
# Hibernate logging
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.jackson.time-zone=Asia/Kolkata
# Maximum file size
spring.servlet.multipart.max-file-size=20MB
# Maximum request size (including all files)
spring.servlet.multipart.max-request-size=120MB
spring.mvc.favicon.enabled=false
file.upload.directory=${user.dir}/secureapi_upload/
dbfot_toilets = dbfot_toilets
# ===============================
# JPA (if using JPA)
# ===============================
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
# ===============================
# API Token
# Default = 24 hours
# ===============================
api.token.ttl-hours=24
# ===============================
# Logging
# ===============================
logging.level.root=INFO
logging.level.in.gccmanpower=INFO

0
src/main/resources/schema.sql

Loading…
Cancel
Save