Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,4 @@ jobs:
name: ledger-jar
path: target/LedgerSystem-0.0.1-SNAPSHOT.jar

# ============================================
# JOB 3: Build Docker Image (Optional)
# ============================================
build-docker:
name: 🐳 Build Docker Image
runs-on: ubuntu-latest
needs: [unit-tests, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- name: 📥 Checkout code
uses: actions/checkout@v3

- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: 🐳 Build Docker image
uses: docker/build-push-action@v4
with:
context: .
push: false
tags: ledger-system:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
22 changes: 12 additions & 10 deletions src/main/java/com/example/ledgersystem/DataSeeder.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.example.ledgersystem.repositories.RoleRepository;
import com.example.ledgersystem.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
Expand All @@ -19,6 +19,7 @@

@Component // <--- Tells Spring to manage this class
@RequiredArgsConstructor
@Slf4j
public class DataSeeder implements CommandLineRunner {
private final PasswordEncoder passwordEncoder;

Expand All @@ -33,31 +34,37 @@ public void run(String... args) throws Exception {

// 1. Check if data already exists so we don't duplicate every restart
if (accountRepository.count() > 0) {
System.out.println("✅ Database already seeded. Skipping...");
log.info("Database already seeded. Skipping data initialization.");
return;
}

log.info("Starting database seed...");

Role userRole = roleRepository
.findByRoleName(AppRoles.ROLE_USER)
.orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_USER)));
log.debug("Role created/found: {}", AppRoles.ROLE_USER);

Role adminRole = roleRepository
.findByRoleName(AppRoles.ROLE_ADMIN)
.orElseGet(() -> roleRepository.save(new Role(AppRoles.ROLE_ADMIN)));
log.debug("Role created/found: {}", AppRoles.ROLE_ADMIN);

User systemAdmin = new User();
systemAdmin.setUsername("systemAdmin");
systemAdmin.setPassword(passwordEncoder.encode("pass123"));
systemAdmin.setRole(List.of(userRole, adminRole));
systemAdmin.setEmail("system@gmail.com");
userRepository.save(systemAdmin);
log.debug("System admin user created: username=systemAdmin");

Account systemAccount = new Account();
systemAccount.setName("CENTRAL_BANK");
systemAccount.setBalance(new BigDecimal("0.00"));
systemAccount.setCurrency("INR");
systemAccount.setUser(systemAdmin);
accountRepository.save(systemAccount);
log.debug("CENTRAL_BANK account created: accountId={}", systemAccount.getAccountId());

// ---- CREATE USERS ----
User aliceUser = new User();
Expand All @@ -79,6 +86,7 @@ public void run(String... args) throws Exception {
charlieUser.setRole(List.of(userRole));

userRepository.saveAll(List.of(aliceUser, bobUser, charlieUser));
log.debug("Test users created: alice, bob, charlie");

// 2. Create Dummy Accounts
Account alice = new Account();
Expand All @@ -102,13 +110,7 @@ public void run(String... args) throws Exception {
// 3. Save to DB
accountRepository.saveAll(Arrays.asList(alice, bob, charlie));

// 4. PRINT UUIDs (Critical for Testing!)
System.out.println("--------------------------------------------");
System.out.println("🎉 DATA SEEDED SUCCESSFULLY");
System.out.println("--------------------------------------------");
System.out.println("👤 Alice UUID: " + alice.getAccountId());
System.out.println("👤 Bob UUID: " + bob.getAccountId());
System.out.println("👤 Charlie UUID: " + charlie.getAccountId());
System.out.println("--------------------------------------------");
log.info("Database seeded successfully. Accounts: alice={}, bob={}, charlie={}",
alice.getAccountId(), bob.getAccountId(), charlie.getAccountId());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.ledgersystem.Exceptions;

import com.example.ledgersystem.Payloads.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
Expand All @@ -12,6 +13,7 @@
import java.util.Map;

@RestControllerAdvice
@Slf4j
public class MyGlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
Expand All @@ -24,33 +26,38 @@ public ResponseEntity<Map<String, String>> myMethodArgumentNotValidException(Met
String message = err.getDefaultMessage();
response.put(fieldName, message);
});
log.warn("Validation failed: fields={}", response.keySet());

return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(AccountNotFound.class) //Our own excep
public ResponseEntity<ApiResponse> ResNotFound(AccountNotFound e) {
log.warn("Account not found: {}", e.getMessage());
String Message = e.getMessage(); //It's in parent class RunTimeExcep that's why we used super
ApiResponse apires = new ApiResponse(Message, false);
return new ResponseEntity<>(apires, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(APIexception.class) //Our own excep If you try to create an already existing category
public ResponseEntity<ApiResponse> myapiexcep(APIexception e) {
log.warn("API exception: {}", e.getMessage());
String Message = e.getMessage(); //It's in parent class RunTimeExcep that's why we used super
ApiResponse apires = new ApiResponse(Message, false);
return new ResponseEntity<>(apires, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(DuplicateTransactionException.class) //Our own excep If you try to create an already existing category
public ResponseEntity<ApiResponse> duplicTransac(DuplicateTransactionException e) {
log.warn("Duplicate transaction: {}", e.getMessage());
String Message = e.getMessage(); //It's in parent class RunTimeExcep that's why we used super
ApiResponse apires = new ApiResponse(Message, false);
return new ResponseEntity<>(apires, HttpStatus.CONFLICT);
}

@ExceptionHandler(InsufficientFundsException.class) //Our own excep If you try to create an already existing category
public ResponseEntity<ApiResponse> insufFunda(InsufficientFundsException e) {
log.warn("Insufficient funds: {}", e.getMessage());
String Message = e.getMessage(); //It's in parent class RunTimeExcep that's why we used super
ApiResponse apires = new ApiResponse(Message, false);
return new ResponseEntity<>(apires, HttpStatus.BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@

import com.example.ledgersystem.model.User;
import com.example.ledgersystem.repositories.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService { //Calls DAO and encoder type shit

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.debug("Loading user by username: {}", username);
User user = userRepository.findByUsername(username)
.orElseThrow(() ->
new UsernameNotFoundException("User not found with given username!!!"));
.orElseThrow(() -> {
log.warn("User not found: username={}", username);
return new UsernameNotFoundException("User not found with given username!!!");
});
log.debug("User loaded successfully: username={}", username);
return UserDetailsImpl.build(user); //Builds the container which stores user data
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
Expand All @@ -16,14 +15,14 @@
import java.util.Map;

@Component
@Slf4j
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
logger.error("Authentication Failed: {}",authException.getMessage());
log.warn("Unauthorized access attempt: path={}, reason={}", request.getServletPath(), authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String,Object> body = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -18,36 +17,40 @@
import java.io.IOException;

@Component
@Slf4j
public class AuthTokenFilter extends OncePerRequestFilter {

@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;

private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try{
String jwt = parseJwt(request); //Extracting token
if(jwt!=null && jwtUtils.validateToken(jwt)){ //Validating token
String username = jwtUtils.getUserNameFromToken(jwt); //Extracting user
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsServiceImpl.loadUserByUsername(username); //Loading userdetails from DB to create a new auth obj
if(jwt != null){
log.debug("JWT token found in request: path={}", request.getServletPath());
if(jwtUtils.validateToken(jwt)){ //Validating token
String username = jwtUtils.getUserNameFromToken(jwt); //Extracting user
log.debug("Valid JWT received for user: {}", username);
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsServiceImpl.loadUserByUsername(username); //Loading userdetails from DB to create a new auth obj

/*This is the child class of actual auth obj*/UsernamePasswordAuthenticationToken authentication = //creating container/auth obj which stores usernamepass and roles
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authentication.setDetails( //Adding all request details(ip address) to authetication obj
new WebAuthenticationDetailsSource().buildDetails(request));
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authentication.setDetails( //Adding all request details(ip address) to authetication obj
new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("User authenticated: {} with authorities={}", username, userDetails.getAuthorities());
}
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e.getMessage());
log.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response); //Telling spring to continue with ita in built filters
}
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/com/example/ledgersystem/Security/jwt/JwtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
Expand All @@ -21,8 +21,8 @@


@Component
@Slf4j
public class JwtUtils {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtUtils.class);

@Value("${spring.app.jwtExpirationMs}")
private long jwtExpirationMs;
Expand All @@ -38,7 +38,7 @@ public class JwtUtils {
//FOR SWAGGER AS VO COOKIE NHI SAMJHTA
public String getJwtFromHeader(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
logger.debug("Authorization Header: {}",bearerToken);
log.debug("Authorization Header: {}", bearerToken);
if(bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); //Remove bearer prefix
}
Expand All @@ -56,6 +56,7 @@ public String getJwtFromCookies(HttpServletRequest request) { //Used in Au

public ResponseCookie generateJwtCookie(UserDetailsImpl userDetails) { //Used in sign in
String jwt = generateTokenFromUsername(userDetails);
log.debug("JWT cookie generated for user: {}", userDetails.getUsername());
ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt)
.path("/api") //Valid within this
.maxAge(24*60*60)
Expand All @@ -74,6 +75,7 @@ public ResponseCookie getCleanCookie() { //Used in sign in
//Generate token from username
public String generateTokenFromUsername(UserDetailsImpl userDetails) {
String username = userDetails.getUsername();
log.debug("Generating JWT token for user: {}", username);
return Jwts.builder()
.subject(username) //setting data
.issuedAt(new Date())
Expand All @@ -100,20 +102,20 @@ public Key key(){
//Validate JWT Token
public boolean validateToken(String token) {
try{
System.out.println("Validate");
log.debug("Validating JWT token");
Jwts.parser()
.verifyWith((SecretKey) key())
.build()
.parseSignedClaims(token);
return true;
}catch(MalformedJwtException exception){
logger.info("Invalid token: {}", exception.getMessage());
log.warn("Invalid JWT token: {}", exception.getMessage());
} catch (ExpiredJwtException e){
logger.error("ExpiredJwtException: {}", e.getMessage());
log.warn("JWT token expired: {}", e.getMessage());
} catch (UnsupportedJwtException e){
logger.error("UnsupportedJwtException: {}", e.getMessage());
log.warn("Unsupported JWT token: {}", e.getMessage());
} catch (IllegalArgumentException e){
logger.error("IllegalArgumentException: {}", e.getMessage());
log.warn("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
Expand Down
Loading
Loading