From 4b41c63ca4e517cca2fead182b60697f81442d6f Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Thu, 23 Apr 2026 22:43:16 -0500 Subject: [PATCH 1/6] feat(docs): add springdoc-openapi-starter-webmvc-ui 2.8.8 dependency --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index f1b90e3..65abfaf 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,12 @@ spring-security-test test + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.8 + From 64e19638d33e4d940a987780995333764b10964a Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Thu, 23 Apr 2026 23:04:55 -0500 Subject: [PATCH 2/6] feat(docs): add OpenApiConfig - JWT Bearer auth, servers, API description --- .../wallet/secure/config/OpenApiConfig.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/main/java/com/wallet/secure/config/OpenApiConfig.java diff --git a/src/main/java/com/wallet/secure/config/OpenApiConfig.java b/src/main/java/com/wallet/secure/config/OpenApiConfig.java new file mode 100644 index 0000000..73613f0 --- /dev/null +++ b/src/main/java/com/wallet/secure/config/OpenApiConfig.java @@ -0,0 +1,105 @@ +package com.wallet.secure.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * OpenAPI 3.0 configuration — generates Swagger UI and API spec. + * + * Accessible at: + * → Swagger UI: http://localhost:8080/swagger-ui/index.html + * → OpenAPI JSON: http://localhost:8080/v3/api-docs + * → OpenAPI YAML: http://localhost:8080/v3/api-docs.yaml + * + * WHY document the API: + * → Reclutadores pueden probar la API sin Postman + * → Frontend developers know exactly what each endpoint expects + * → Auto-generated from annotations — always in sync with code + * + * Security scheme: Bearer JWT + * → "Authorize" button in Swagger UI accepts the access token + * → All authenticated endpoints show the lock icon + * → Testers can login, copy the token, paste in Authorize → test everything + */ +@Configuration +public class OpenApiConfig { + + private static final String SECURITY_SCHEME_NAME = "bearerAuth"; + + @Bean + public OpenAPI secureWalletOpenAPI() { + return new OpenAPI() + .info(buildInfo()) + .servers(buildServers()) + .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) + .components(new Components() + .addSecuritySchemes(SECURITY_SCHEME_NAME, buildSecurityScheme())); + } + + private Info buildInfo() { + return new Info() + .title("Secure Wallet API") + .version("0.0.1-SNAPSHOT") + .description(""" + ## Digital Wallet with OWASP Security Best Practices + + A production-grade REST API implementing: + - **JWT Authentication** with access + refresh tokens + - **Multi-device session management** (user_sessions table) + - **ACID-compliant financial transactions** with pessimistic locking + - **Complete audit trail** (audit_logs + transaction_history) + - **OWASP Top 10** mitigations throughout + + ### How to authenticate: + 1. `POST /auth/register` or `POST /auth/login` + 2. Copy the `accessToken` from the response + 3. Click **Authorize** → paste `Bearer {accessToken}` + 4. All authenticated endpoints are now accessible + + ### Roles: + - `USER` — standard wallet operations + - `ADMIN` — user management, wallet suspension, session audit + """) + .contact(new Contact() + .name("DJAngel973") + .url("https://github.com/DJAngel973/Secure-Wallet-API")) + .license(new License() + .name("MIT") + .url("https://opensource.org/licenses/MIT")); + } + + private List buildServers() { + return List.of( + new Server() + .url("http://localhost:8080") + .description("Local development"), + new Server() + .url("https://api.securewallet.dev") + .description("Production (future)") + ); + } + + /** + * Defines the JWT Bearer security scheme. + * This adds the "Authorize" button to Swagger UI. + * Users paste their accessToken once — all endpoints use it automatically. + */ + private SecurityScheme buildSecurityScheme() { + return new SecurityScheme() + .name(SECURITY_SCHEME_NAME) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .description("Paste your access token here (without 'Bearer ' prefix)"); + } +} \ No newline at end of file From e56d3c36b948786e1d2a6120a12eaf1ce3c25a8c Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Thu, 23 Apr 2026 23:07:39 -0500 Subject: [PATCH 3/6] feat(docs): add Swagger UI paths to PUBLIC_ENDPOINTS in SecurityConfig --- src/main/java/com/wallet/secure/config/SecurityConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wallet/secure/config/SecurityConfig.java b/src/main/java/com/wallet/secure/config/SecurityConfig.java index 9803854..28558e0 100644 --- a/src/main/java/com/wallet/secure/config/SecurityConfig.java +++ b/src/main/java/com/wallet/secure/config/SecurityConfig.java @@ -49,7 +49,11 @@ public class SecurityConfig { "/auth/login", "/auth/register", "/auth/refresh", - "/actuator/health" + "/actuator/health", + "/swagger-ui/**", + "/swagger-ui.html", + "/v3/api-docs/**", + "/v3/api-docs" }; /** From 2fc5fcd6ced016a7074503c558ebdf3be5dfc433 Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Thu, 23 Apr 2026 23:12:58 -0500 Subject: [PATCH 4/6] feat(docs): configure SpringDoc in application.yml - UI sorting, try-it-out enabled --- src/main/resources/application.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a76291d..c9374e4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -133,4 +133,17 @@ app: password-require-uppercase: true password-require-lowercase: true password-require-digit: true - password-require-special: true \ No newline at end of file + password-require-special: true + + # SPRINGDOC - OpenAPI / Swagger + springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui/index.html + operations-sorter: method # Sort by HTTP method (GET, POST, PUT, DELETE) + tags-sorter: alpha # Sort tags alphabetically + display-request-duration: true # Show response time in UI + try-it-out-enabled: true # Enable "Try it out" by default + show-actuator: false # Don't expose actuator in Swagger + packages-to-scan: com.wallet.secure # Only scan our packages \ No newline at end of file From 7f737883156b3d8cc3321d4fb56dcbda43a8ef80 Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Thu, 23 Apr 2026 23:36:38 -0500 Subject: [PATCH 5/6] feat(docs): add @Tag to all controllers - numbered groups in Swagger UI --- .../com/wallet/secure/audit/controller/AuditController.java | 6 ++---- .../com/wallet/secure/auth/controller/AuthController.java | 2 ++ .../wallet/secure/auth/controller/SessionController.java | 2 ++ .../transaction/controller/TransactionController.java | 2 ++ .../controller/TransactionHistoryController.java | 2 ++ .../com/wallet/secure/user/controller/UserController.java | 2 ++ .../wallet/secure/wallet/controller/WalletController.java | 2 ++ 7 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/wallet/secure/audit/controller/AuditController.java b/src/main/java/com/wallet/secure/audit/controller/AuditController.java index 8161456..423e53d 100644 --- a/src/main/java/com/wallet/secure/audit/controller/AuditController.java +++ b/src/main/java/com/wallet/secure/audit/controller/AuditController.java @@ -1,23 +1,20 @@ package com.wallet.secure.audit.controller; import com.wallet.secure.audit.dto.AuditLogResponse; -import com.wallet.secure.audit.entity.AuditLog; import com.wallet.secure.audit.repository.AuditLogRepository; import com.wallet.secure.common.enums.AuditAction; import com.wallet.secure.common.enums.LogSeverity; import com.wallet.secure.common.response.ApiResponse; -import jakarta.validation.constraints.Size; -import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.web.PageableDefault; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.time.Instant; import java.time.LocalDate; @@ -55,6 +52,7 @@ @RequestMapping("/audit") @RequiredArgsConstructor @Log4j2 +@Tag(name = "7. Audit Logs", description = "Security audit trail — ADMIN only") @PreAuthorize("hasRole('ADMIN')") public class AuditController { diff --git a/src/main/java/com/wallet/secure/auth/controller/AuthController.java b/src/main/java/com/wallet/secure/auth/controller/AuthController.java index 78cca8b..e45020d 100644 --- a/src/main/java/com/wallet/secure/auth/controller/AuthController.java +++ b/src/main/java/com/wallet/secure/auth/controller/AuthController.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.tags.Tag; /** * REST Controller for authentication endpoints. @@ -39,6 +40,7 @@ @RequestMapping("/auth") @RequiredArgsConstructor @Log4j2 +@Tag(name = "1. Authentication", description = "Register, login, logout and token refresh — public endpoints") public class AuthController { private final AuthService authService; diff --git a/src/main/java/com/wallet/secure/auth/controller/SessionController.java b/src/main/java/com/wallet/secure/auth/controller/SessionController.java index 50682f4..f483a30 100644 --- a/src/main/java/com/wallet/secure/auth/controller/SessionController.java +++ b/src/main/java/com/wallet/secure/auth/controller/SessionController.java @@ -12,6 +12,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; @@ -48,6 +49,7 @@ @RequestMapping("/sessions") @RequiredArgsConstructor @Log4j2 +@Tag(name = "2. Sessions", description = "Multi-device session management — see and revoke active sessions") public class SessionController { private final SessionService sessionService; diff --git a/src/main/java/com/wallet/secure/transaction/controller/TransactionController.java b/src/main/java/com/wallet/secure/transaction/controller/TransactionController.java index c0c7fa6..8d54aa7 100644 --- a/src/main/java/com/wallet/secure/transaction/controller/TransactionController.java +++ b/src/main/java/com/wallet/secure/transaction/controller/TransactionController.java @@ -19,6 +19,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.UUID; @@ -47,6 +48,7 @@ @RequestMapping("/transactions") @RequiredArgsConstructor @Log4j2 +@Tag(name = "5. Transactions", description = "Deposit, withdraw and transfer funds between wallets") public class TransactionController { private final TransactionService transactionService; diff --git a/src/main/java/com/wallet/secure/transaction/controller/TransactionHistoryController.java b/src/main/java/com/wallet/secure/transaction/controller/TransactionHistoryController.java index 36b2a40..1c4ab29 100644 --- a/src/main/java/com/wallet/secure/transaction/controller/TransactionHistoryController.java +++ b/src/main/java/com/wallet/secure/transaction/controller/TransactionHistoryController.java @@ -12,6 +12,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; @@ -51,6 +52,7 @@ @RestController @RequiredArgsConstructor @Log4j2 +@Tag(name = "6. Transaction History", description = "Complete state timeline for each transaction — PENDING → PROCESSING → COMPLETED/FAILED") public class TransactionHistoryController { private final TransactionHistoryService historyService; diff --git a/src/main/java/com/wallet/secure/user/controller/UserController.java b/src/main/java/com/wallet/secure/user/controller/UserController.java index 030282a..83ce46a 100644 --- a/src/main/java/com/wallet/secure/user/controller/UserController.java +++ b/src/main/java/com/wallet/secure/user/controller/UserController.java @@ -12,6 +12,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.UUID; @@ -34,6 +35,7 @@ @RequestMapping("/users") @RequiredArgsConstructor @Log4j2 +@Tag(name = "3. Users", description = "User profile, password change and account management") public class UserController { private final UserService userService; diff --git a/src/main/java/com/wallet/secure/wallet/controller/WalletController.java b/src/main/java/com/wallet/secure/wallet/controller/WalletController.java index 1f845f2..31aad1e 100644 --- a/src/main/java/com/wallet/secure/wallet/controller/WalletController.java +++ b/src/main/java/com/wallet/secure/wallet/controller/WalletController.java @@ -14,6 +14,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.UUID; @@ -41,6 +42,7 @@ @RequestMapping("/wallets") @RequiredArgsConstructor @Log4j2 +@Tag(name = "4. Wallets", description = "Create and manage multi-currency digital wallets") public class WalletController { private final WalletService walletService; From 6c666e8b2981cd2039db4972fa229834acecda6d Mon Sep 17 00:00:00 2001 From: DJAngel973 Date: Fri, 24 Apr 2026 22:39:58 -0500 Subject: [PATCH 6/6] docs: add AGENTS.md - agent context, coding rules, security patterns and test guidelines --- AGENTS.md | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f2b0ac4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,259 @@ +# AGENTS.md — Secure Wallet API + +> This file is the single source of truth for any AI agent working on this project. +> Read it completely before generating, modifying, or reviewing any code. + +--- + +## What This Project Is + +A production-grade REST API for a digital wallet. +Academic/portfolio focus — security and traceability are first-class concerns. + +**Core priorities (in order):** +1. Security — OWASP Top 10 (2025) compliance throughout +2. Correctness — ACID financial transactions, no money lost +3. Auditability — every sensitive operation logged +4. Maintainability — clean, documented, testable code + +--- + +## Technology Stack + +| Component | Technology | Version | +|-------------|-----------------------------|---------------| +| Framework | Spring Boot | 3.4.2 | +| Language | Java | 17 | +| Security | Spring Security + JWT | jjwt 0.12.6 | +| Database | PostgreSQL | 16 | +| ORM | Spring Data JPA / Hibernate | — | +| Logging | Log4j2 | 2.25.3 | +| Validation | Spring Bean Validation | — | +| Boilerplate | Lombok | 1.18.36 | +| Docs | SpringDoc OpenAPI | 2.8.8 | +| Tests | JUnit 5 + Mockito | — | +| CI/CD | GitHub Actions + CodeQL | — | + +--- + +## Project Structure — Domain-Driven + +``` +com.wallet.secure/ +├── config/ # SecurityConfig, OpenApiConfig, AuditConfig +├── auth/ # Authentication: AuthService, JwtService, SessionService +│ ├── controller, dto, entity, repository, security, service +├── user/ # Users and profiles +├── wallet/ # Wallets and balances +├── transaction/ # Transactions + TransactionHistory (business core) +├── audit/ # AuditService, AuditLog — security event trail +└── common/ # ApiResponse, exceptions, enums, validators +``` + +**Rule: NEVER create root-level folders by type.** +Each domain contains its own controller/, dto/, entity/, repository/, service/. + +--- + +## Architecture Rules — ALWAYS Follow + +### 1. API Responses +Every endpoint returns `ApiResponse` from `common/response/`: +```java +return ResponseEntity.ok(ApiResponse.ok("Message", data)); +return ResponseEntity.ok(ApiResponse.error("Message")); +``` +Never return raw objects or entities directly. + +### 2. Database +- `ddl-auto: validate` — Hibernate NEVER creates or modifies tables +- Schema is managed exclusively via SQL scripts in `/database/` +- Before adding any entity field, add the column to the SQL script first +- Migrations go in `database/08-migrations.sql` + +### 3. Security — userId Always From JWT +```java +// CORRECT — identity from trusted JWT: +@AuthenticationPrincipal UserDetails userDetails +UUID userId = resolveUserId(userDetails.getUsername()); + +// WRONG — never trust userId from request body or path for ownership: +@RequestBody SomeRequest request // where request contains userId +``` + +### 4. Transactions +Financial operations MUST be `@Transactional`: +```java +@Transactional +public ApiResponse deposit(...) { ... } +``` +Pessimistic locking for balance updates: `walletRepository.findByIdWithLock(id)` + +### 5. OWASP Comments +Every security decision must reference OWASP: +```java +// OWASP A07: brute force detection — 5 failures in 15 min → CRITICAL alert +// OWASP A01: userId from JWT — user cannot access another user's data +// OWASP A02: SHA-256 for token hashing — BCrypt unnecessary for random tokens +``` + +--- + +## Code Conventions + +### Naming +- Entities: singular PascalCase → `User`, `Wallet`, `Transaction` +- Request DTOs: `[Action]Request` → `LoginRequest`, `DepositRequest` +- Response DTOs: `[Entity]Response` → `UserResponse`, `WalletResponse` +- Services: `[Domain]Service` → `AuthService`, `TransactionHistoryService` + +### Comments +Document the **WHY**, not the what: +```java +// WHY @Async: audit writes must never slow down a financial transaction +// WHY Propagation.REQUIRES_NEW: failure audit must survive parent transaction rollback +``` + +### Lombok +Use `@Getter`, `@Builder`, `@RequiredArgsConstructor`, `@NoArgsConstructor`, `@AllArgsConstructor`. +Never use `@Data` on entities — too broad, causes issues with JPA proxies. + +--- + +## Testing Rules — CRITICAL + +### Pattern: Unit tests only (no Spring context) +```java +@ExtendWith(MockitoExtension.class) // ← always this, never @SpringBootTest +class SomeServiceTest { + @Mock private SomeDependency dep; + @InjectMocks private SomeService service; +} +``` + +### Reference implementations +Before writing any test, read these existing tests as reference: +- `AuditServiceTest.java` → how to test a service with `ArgumentCaptor` +- `AuthServiceTest.java` → how to mock `HttpServletRequest` +- `TransactionServiceTest.java` → how to test financial operations with `@Nested` + +### Test structure +```java +@Test +@DisplayName("action_condition_expectedResult") +void methodName_condition_expectation() { + // GIVEN + // WHEN + // THEN +} +``` + +### What to always test +- Happy path ✅ +- Failure path (insufficient balance, not found, unauthorized) ✅ +- Balance unchanged after failed operation (ACID) ✅ +- Exception type AND message ✅ +- `assertThatNoException()` for fire-and-forget operations ✅ + +### What NOT to test +- `@Async` behavior → requires Spring context +- `@Transactional` behavior → requires Spring context +- Hibernate queries → requires DB + +--- + +## Security Rules — NEVER Violate + +1. Never `ddl-auto: create` or `update` in any environment +2. Stack traces never reach the client — `GlobalExceptionHandler` handles all +3. Passwords never logged, never returned in any response +4. JWT secret always from environment variable `JWT_SECRET` +5. Financial operations always inside `@Transactional` +6. Every sensitive operation generates an `audit_logs` entry via `AuditService` +7. Input validation required on ALL endpoints (`@Valid` in controller) +8. `userId` always from JWT — never from request body for ownership checks +9. SHA-256 for token hashing (not BCrypt — tokens are random, not passwords) +10. Raw tokens never stored in DB — only their SHA-256 hash + +--- + +## Existing Security Patterns — Reuse Them + +### Audit on every sensitive operation +```java +// Called after any auth event, wallet change, or transaction: +auditService.logLoginSuccess(userId, email, ip, userAgent); +auditService.logTransactionSuccess(userId, txId, type, amount, currency, ip, ua); +auditService.logWalletCreated(userId, walletId, currency, ip, ua); +``` + +### IP + UserAgent extraction +```java +// Already implemented in AuthService — copy this pattern: +private String extractIp(HttpServletRequest request) { ... } +private String extractUserAgent(HttpServletRequest request) { ... } +``` + +### Session validation on refresh +```java +// AuthService.refresh() — dual validation pattern: +if (!refreshToken.equals(user.getRefreshToken())) { throw ... } // legacy +sessionService.validateSession(refreshToken); // new +``` + +### Ownership check pattern +```java +// User can only access their own resources: +transactionRepository.findByIdAndUserId(id, userId) + .orElseThrow(() -> new ResourceNotFoundException("Not found")); +// Returns 404 (not 403) — prevents resource enumeration (OWASP A01) +``` + +--- + +## Domain Summary — What Exists + +| Domain | Entity | Key Service | Status | +|-------------|----------------------|------------------------------|-----------| +| auth | — | AuthService, SessionService | ✅ Complete | +| user | User | UserService | ✅ Complete | +| wallet | Wallet | WalletService | ✅ Complete | +| transaction | Transaction | TransactionService | ✅ Complete | +| history | TransactionHistory | TransactionHistoryService | ✅ Complete | +| audit | AuditLog | AuditService | ✅ Complete | +| sessions | Session | SessionService | ✅ Complete | +| docs | — | OpenApiConfig | ✅ Complete | + +--- + +## Branching Strategy + +``` +main ← Production. Protected. Merge via PR only. +feature/* ← New features: feature/descriptive-name +fix/* ← Bug fixes: fix/bug-name +``` + +**Commit format (Conventional Commits):** +``` +feat(domain): short description +fix(domain): short description +test(domain): short description +docs: short description +refactor(domain): short description +``` + +--- + +## Pull Request Checklist + +Before opening a PR, verify: +- [ ] Unit tests for all new business logic +- [ ] No hardcoded secrets — environment variables only +- [ ] All exceptions handled via `GlobalExceptionHandler` +- [ ] `@Valid` on all controller endpoints +- [ ] `AuditService` called for sensitive operations +- [ ] `@Transactional` on financial operations +- [ ] OWASP comments on security decisions +- [ ] `@Schema` on new DTOs for Swagger +- [ ] `@Tag` on new controllers for Swagger grouping \ No newline at end of file