From 4cc522a0bd62ba68918af7b8f56d80562d0536b4 Mon Sep 17 00:00:00 2001 From: DJAngel973 <189556164+DJAngel973@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:58:11 -0500 Subject: [PATCH 1/4] docs: update status project on readme --- README.md | 451 +++++++++++++++--------------------------------------- 1 file changed, 124 insertions(+), 327 deletions(-) diff --git a/README.md b/README.md index a0dcac2..5aa1d9a 100644 --- a/README.md +++ b/README.md @@ -1,259 +1,140 @@ -# 🔐 Secure Wallet API +# Secure Wallet API -A **learning project** built to explore real-world security practices in the context of a digital savings wallet REST API. +Multi-currency digital savings wallet — REST API with authentication, transactions, and audit trail. -Built with **Java 17**, **Spring Boot 3.4.2**, **PostgreSQL 16** and **Docker**. +Built with **Java 17**, **Spring Boot 3.4.2**, and **PostgreSQL 16**. -> **Security is a first-class concern here — not an afterthought.** -> Every architectural decision is documented, every security rule references OWASP, and every sensitive operation is audited. +> This is a learning project built with AI assistance to explore how a savings wallet API works under the hood — authentication flows, ACID financial transactions, audit logging, and security practices. Some sections still need review and polish. --- -## 🎯 What This Project Is +## What is this -This is an academic/portfolio project designed to answer a specific question: +A REST API for a digital savings wallet where users can: -> *How would a financial API look if security, auditability, and correctness were treated as requirements from day one — before a single line of business code is written?* +- Register and manage their profile +- Create savings wallets in multiple currencies (USD, EUR, COP, MXN, ARS) +- Deposit, withdraw, and transfer funds between wallets +- Track every operation through a full audit trail +- Authenticate with dual-token JWT and optional two-factor authentication -The result is a digital savings wallet where users can deposit, withdraw, and transfer money between accounts — built with the same security discipline you'd expect in a real fintech product. - -**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 and traceable -4. **Maintainability** — clean, documented, testable code +The idea came from wanting to understand the moving parts of a real savings wallet — not just the happy path, but edge cases, concurrency, token rotation, and what happens when things go wrong. --- -## ✨ Features +## Features -### 👤 User Management -- Register, update profile, and soft-delete accounts -- Role-based access control: `USER`, `ADMIN`, `MANAGER` -- Account lockout after 5 failed login attempts (30-minute cooldown) -- Two-Factor Authentication (2FA / TOTP — RFC 6238, Google Authenticator compatible) -- Email verification flag and login tracking +### User Management +- Registration, profile updates, and soft-delete accounts +- Role-based access: `USER`, `ADMIN`, `MANAGER` +- Account lockout after repeated failed login attempts +- Two-Factor Authentication via TOTP (compatible with Google Authenticator, Authy) -### 💰 Savings Wallets +### Multi-Currency Savings Wallets - One wallet per user per currency -- Multi-currency support: USD, EUR, COP, MXN, ARS -- Real-time balance with ACID-guaranteed updates +- Supported currencies: USD, EUR, COP, MXN, ARS - Wallet states: `ACTIVE`, `SUSPENDED`, `CLOSED` +- Balance updates with pessimistic locking -### 💸 Financial Transactions +### Financial Transactions | Operation | Description | |-----------|-------------| -| **Deposit** | Add funds from an external source into a wallet | -| **Withdrawal** | Move funds from a wallet to an external destination | -| **Transfer** | Move funds between any two wallets in the system | -| **History** | Full state-change log per transaction | - -- 2FA required for operations above **$100** -- Transaction limit: $10,000 | Daily limit: $50,000 -- Pessimistic locking (`FOR UPDATE`) on all balance reads +| Deposit | Add funds from an external source | +| Withdrawal | Move funds out to an external destination | +| Transfer | Move funds between two wallets in the system | +| History | State-change log per transaction | + +- Configurable limits: max per transaction, max per day +- 2FA threshold: operations above a configurable amount require a TOTP code +- Pessimistic locking on balance reads to prevent race conditions - Unique reference code per transaction -- JSONB metadata (IP, device, geolocation) for fraud detection - -### 🔒 Security & DevSecOps -- Dual-token JWT: Access Token (15 min) + Refresh Token (7 days) -- Refresh Token rotation and revocation stored in the database -- SHA-256 for token hashing — BCrypt only for passwords -- UUID primary keys on all entities (prevents enumeration) -- Stack traces never exposed to the client -- OWASP Dependency Check in CI — **fails build on CVSS ≥ 7** -- CodeQL static analysis on every PR - -### 📋 Audit Trail -- Independent `audit/` domain — auditing is a business requirement, not a cross-cutting concern -- Every sensitive operation generates an `audit_logs` entry -- IP address, User-Agent, timestamp, and JSONB details per event -- PostgreSQL triggers for automatic state-change auditing + +### Authentication & Security +- Dual-token JWT: Access Token (short-lived) + Refresh Token (longer-lived with rotation) +- Refresh token stored as SHA-256 hash — real revocation on logout +- UUID primary keys on all entities (no sequential IDs) +- Stack traces never reach the client — all errors go through a centralized handler +- Input validation on every endpoint + +### Audit Trail +- Every sensitive operation generates a log entry +- IP address, User-Agent, timestamp, and contextual details per event +- Database triggers for automatic state-change tracking --- -## 🏗️ Architecture +## Architecture -**Pattern:** Hybrid Domain-Driven Design — layers organized **by domain**, never by class type. +**Pattern:** Domain-Driven Design — code organized by business domain, not by layer type. ``` com.wallet.secure/ ├── config/ # SecurityConfig, OpenApiConfig, AuditConfig, JwtConfig ├── auth/ # Authentication: JWT, 2FA, sessions -│ ├── controller/ # AuthController, SessionController -│ ├── dto/ # LoginRequest, AuthResponse, RefreshTokenRequest -│ ├── entity/ # Session -│ ├── repository/ # SessionRepository -│ ├── security/ # JwtAuthFilter, JwtService, UserDetailsServiceImpl -│ └── service/ # AuthService, SessionService +│ ├── controller/, dto/, entity/, repository/, security/, service/ ├── user/ # Users and profiles ├── wallet/ # Wallets and balances -├── transaction/ # Transactions + history (business core) -├── audit/ # Security audit trail +├── transaction/ # Transactions and history (business core) +├── audit/ # Security audit log └── common/ # ApiResponse, exceptions, enums, validators ``` -**Architecture rule:** NEVER create root-level folders by type (`controllers/`, `services/`). Each domain owns its own layers. - -> 📖 [`CONTEXT.md`](CONTEXT.md) — Single source of truth: full architecture, stack, conventions, and rules. -> 📖 [`DECISIONS.md`](DECISIONS.md) — 8 Architecture Decision Records with full reasoning for every major choice. - ---- - -## 🛡️ Security Model - -### Why Authentication and Authorization Matter Here - -A wallet API without a solid auth model is not a wallet — it's an open bank account. - -This project enforces two distinct layers: - -**Authentication** — *Who are you?* -Dual-token JWT ensures a stolen Access Token is useless after 15 minutes. The Refresh Token is stored as a SHA-256 hash in the database, enabling real revocation on logout. Sessions are tracked per device. - -**Authorization** — *What are you allowed to do?* -`userId` is **always** extracted from the JWT — never from the request body or path parameters. This means a user cannot access another user's wallet by simply changing an ID in the URL. Resources respond with `404` (not `403`) to prevent enumeration. - -``` -POST /auth/login - → Validate credentials (BCrypt password check) - → Check account not locked - → If operation requires 2FA → POST /auth/2fa/verify - → Issue Access Token (15 min) + Refresh Token (7 days) - → Register session in DB - → Audit log event - → Return tokens - -POST /auth/refresh - → Validate Refresh Token (signature + expiry + exists in DB) - → Rotate token (invalidate old, issue new) - → Return new Access Token + new Refresh Token - -POST /auth/logout - → Revoke Refresh Token in DB - → Audit log event - → Access Token expires naturally within 15 min -``` - -### Session Parameters - -| Parameter | Value | Reason | -|-----------|-------|--------| -| Access Token TTL | 15 minutes | Limits exposure if stolen | -| Refresh Token TTL | 7 days | Security/UX balance | -| Max inactivity | 30 minutes | Banking standard | -| Concurrent sessions | Allowed | Tracked per device in DB | -| 2FA threshold | $100 | Protects everyday transactions, not just large ones | - -### OWASP Top 10 (2025) Coverage - -> OWASP Top 10 2025 was published on November 6, 2025 at the Global AppSec Conference in Washington D.C. - -| # | Risk | Status | How it is addressed | -|---|------|--------|---------------------| -| A01 | Broken Access Control *(includes SSRF)* | ✅ | `userId` from JWT only — never from request. `404` on ownership mismatch. RBAC via Spring Security. API makes no outbound calls (SSRF N/A) | -| A02 | Cryptographic Failures | ✅ | BCrypt strength 12 for passwords. SHA-256 for token hashing. JWT signed with HS256. HTTPS required in production. `DECIMAL(19,4)` for money — no float precision errors | -| A03 | Software Supply Chain Failures 🆕 | ✅ | OWASP Dependency Check in CI (`failOnCVSS ≥ 7`). All dependency versions pinned in `pom.xml` | -| A04 | Injection | ✅ | Hibernate prepared statements on all queries. Bean Validation (`@Valid`) on every endpoint input | -| A05 | Security Misconfiguration | ✅ | Error messages never expose internals. Stack traces never sent to client. No default credentials. Secure HTTP headers. Limited Actuator exposure | -| A06 | Insecure Design | ✅ | Documented threat model. Transaction limits. 2FA threshold at $100. Pessimistic locking for concurrent balance reads | -| A07 | Identification & Authentication Failures | ✅ | Dual-token JWT (15 min / 7 days). TOTP 2FA (RFC 6238). Account lockout. 30-min inactivity. Refresh Token rotation and revocation | -| A08 | Software and Data Integrity Failures | ✅ | CodeQL static analysis in GitHub Actions. SQL schema versioned in Git. Hibernate `ddl-auto: validate` | -| A09 | Security Logging & Monitoring Failures | ✅ | Every security event logged to `audit_logs`. Log4j2 async logging. No passwords or tokens ever written to logs | -| A10 | Mishandling of Exceptional Conditions 🆕 | ✅ | Centralized `GlobalExceptionHandler` — never "fail open". No sensitive data in error responses | - ---- - -## 🗄️ Database - -PostgreSQL 16 with schema managed exclusively by versioned SQL scripts. Hibernate only validates — never creates or modifies tables. - -| # | Script | Purpose | -|---|--------|---------| -| 01 | `extensions.sql` | PostgreSQL extensions: `pgcrypto`, `uuid-ossp` | -| 02 | `types.sql` | 7 custom ENUMs (roles, currencies, statuses) | -| 03 | `tables.sql` | 6 core tables with constraints and FK rules | -| 04 | `index.sql` | Partial, composite, and GIN indexes | -| 05 | `triggers.sql` | Auto-update timestamps, balance validation, state-change audit | -| 06 | `functions.sql` | `process_transaction()` ACID, `cleanup_expired_sessions()` | -| 07 | `seed.sql` | Development test data only | -| 08 | `migrations.sql` | Incremental schema changes | - -> 📖 [`database/README.md`](database/README.md) — Full ER diagram, FK rules, and DB security design decisions. +Each domain owns its controllers, DTOs, entities, repositories, and services. Nothing lives at the root level grouped by type. --- -## 🛠️ Tech Stack - -| Component | Technology | Version | -|-----------|-----------|---------| -| Language | Java | 17 | -| Framework | Spring Boot | 3.4.2 | -| Security | Spring Security + JWT | jjwt 0.12.6 | -| Database | PostgreSQL | 16-alpine | -| ORM | Spring Data JPA / Hibernate | — | -| Logging | Log4j2 | 2.25.3 | -| Validation | Spring Bean Validation | — | -| Boilerplate | Lombok | 1.18.36 | -| API Docs | SpringDoc OpenAPI | 2.8.8 | -| Tests | JUnit 5 + Mockito + AssertJ | — | -| Containers | Docker + Docker Compose | — | -| CI/CD | GitHub Actions | — | -| Static Analysis | CodeQL | — | -| Dependency Scanning | OWASP Dependency Check | 12.2.0 | +## Tech Stack + +| Component | Technology | +|-----------|------------| +| Language | Java 17 | +| Framework | Spring Boot 3.4.2 | +| Security | Spring Security + jjwt 0.12.6 | +| Database | PostgreSQL 16 (Docker) | +| ORM | Spring Data JPA / Hibernate | +| Logging | Log4j2 | +| Validation | Spring Bean Validation | +| API Docs | SpringDoc OpenAPI 2.8.8 (Swagger) | +| Boilerplate | Lombok | +| Tests | JUnit 5 + Mockito + AssertJ | +| CI/CD | GitHub Actions + CodeQL + OWASP Dependency Check | --- -## 🚀 Getting Started +## Getting Started ### Prerequisites - Java 17+ -- Docker & Docker Compose -- Maven 3.9+ (Maven wrapper `./mvnw` included) +- Docker and Docker Compose -### Run locally +### Setup ```bash -# 1. Clone the repository git clone https://github.com/DJAngel973/Secure-Wallet-API.git cd Secure-Wallet-API -# 2. Set up environment variables +# Copy and fill in the environment variables cp .env.example .env -# Edit .env with your local values (DB credentials, JWT secret, ports) -# 3. Start everything (automated script) -./script/dev-start.sh +# Start PostgreSQL +docker compose up -d -# Or step by step: -docker compose up -d # PostgreSQL only -docker compose --profile tools up -d # PostgreSQL + pgAdmin at http://localhost:5050 +# Run the application (dev profile) ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev ``` -### Stop - -```bash -./script/dev-stop.sh -``` - ### Run tests ```bash -./mvnw test # All unit tests (H2 in-memory, no DB needed) -./mvnw test -Dtest=TransactionServiceTest # Single test class +./mvnw test # All unit tests (uses H2 in-memory, no DB needed) +./mvnw test -Dtest=TransactionServiceTest # Single test class ``` ---- - -## 📡 Using the API - -Once the application is running, the full interactive API documentation is available at: +### Interactive API docs -``` -http://localhost:8080/swagger-ui.html -``` +Once running: [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) -All endpoints return a standard `ApiResponse` envelope: +All responses follow a standard wrapper: ```json { @@ -263,162 +144,78 @@ All endpoints return a standard `ApiResponse` envelope: } ``` -### 1. Register a new user - -```http -POST /auth/register -Content-Type: application/json - -{ - "email": "alice@example.com", - "password": "SecureP@ss1", - "firstName": "Alice", - "lastName": "Smith" -} -``` - -### 2. Login and get tokens - -```http -POST /auth/login -Content-Type: application/json - -{ - "email": "alice@example.com", - "password": "ExampleSecureP@ss1" -} -``` - -```json -{ - "success": true, - "message": "Login successful", - "data": { - "accessToken": "ExampleekeypracticeyJhbGc...", - "refreshToken": "ExampleekeypracticeyJhbGcNiJ9...", - "tokenType": "Bearer", - "expiresIn": 900 - } -} -``` - -> Use the `accessToken` as a Bearer token in the `Authorization` header for all protected requests. - -### 3. Get your own profile - -```http -GET /users/me -Authorization: Bearer -``` - -### 4. Create a wallet - -```http -POST /wallets -Authorization: Bearer -Content-Type: application/json - -{ - "currency": "USD" -} -``` - -### 5. Deposit funds - -```http -POST /transactions/deposit -Authorization: Bearer -Content-Type: application/json - -{ - "walletId": "ExampleekeypracticeyJhbGcafa6", - "amount": 500.00, - "description": "Initial deposit" -} -``` - -### 6. Transfer between wallets - -> Amounts above **$100** require a valid TOTP code. Add the `X-2FA-Code` header with the 6-digit code from your authenticator app. +--- -```http -POST /transactions/transfer -Authorization: Bearer -X-2FA-Code: 123456 -Content-Type: application/json +**Note:** endpoints listed in Features above have not been tested end-to-end yet. The Swagger UI at `http://localhost:8080/swagger-ui.html` lists all available endpoints for exploration when the app is running. -{ - "sourceWalletId": "ExampleekeypracticeyJhbGc-2c963f66afa6", - "targetWalletId": "ExampleekeypracticeyJhbGc-3d074g77bgb7", - "amount": 150.00, - "description": "Splitting dinner" -} -``` - -### 7. Get transaction history for a wallet +--- -```http -GET /transactions/wallet/ExampleekeypracticeyJhbGcafa6 -Authorization: Bearer -``` +## Database -### 8. Refresh your access token +PostgreSQL 16 with schema managed through versioned SQL scripts — Hibernate validates but never creates or modifies tables. -```http -POST /auth/refresh -Content-Type: application/json +| # | Script | Purpose | +|---|--------|---------| +| 01 | `extensions.sql` | PostgreSQL extensions (pgcrypto, uuid-ossp) | +| 02 | `types.sql` | Custom ENUMs (roles, currencies, statuses) | +| 03 | `tables.sql` | Core tables with constraints and foreign keys | +| 04 | `index.sql` | Partial, composite, and GIN indexes | +| 05 | `triggers.sql` | Auto-timestamps, balance validation, state-change audit | +| 06 | `functions.sql` | `process_transaction()` with ACID guarantees | +| 07 | `seed.sql` | Development test data | +| 08 | `migrations.sql` | Incremental schema changes | -{ - "refreshToken": "ExampleekeypracticeyJhbGcNiJ9..." -} -``` +--- -### 9. Logout +## OWASP Top 10 (2025) coverage -```http -POST /auth/logout -Authorization: Bearer -Content-Type: application/json +This project uses the OWASP Top 10 as a guide for security decisions. Here is what is currently addressed: -{ - "refreshToken": "ExampleekeypracticeyJhbGcNiJ9..." -} -``` +| # | Risk | How it is handled | +|---|------|-------------------| +| A01 | Broken Access Control | `userId` always from JWT — never from request body. `404` on ownership mismatch. RBAC via Spring Security. | +| A02 | Cryptographic Failures | BCrypt strength 12 for passwords. SHA-256 for token hashing. `DECIMAL(19,4)` for amounts. | +| A03 | Software Supply Chain Failures | OWASP Dependency Check in CI. Pinned dependency versions. | +| A04 | Injection | Hibernate prepared statements. Bean Validation on all inputs. | +| A05 | Security Misconfiguration | No internals exposed in errors. No default credentials. Secure headers. | +| A06 | Insecure Design | Transaction and daily limits. 2FA threshold. Pessimistic locking on balance reads. | +| A07 | Auth Failures | Dual-token JWT with rotation. Account lockout. TOTP 2FA. | +| A08 | Data Integrity Failures | CodeQL in CI. SQL scripts versioned in Git. `ddl-auto: validate`. | +| A09 | Logging & Monitoring | All security events written to `audit_logs`. No passwords or tokens in logs. | +| A10 | Mishandled Exceptions | Centralized exception handler. Never "fail open". | --- -## 🔄 CI/CD Pipeline +## CI/CD -| Job | Trigger | Description | -|-----|---------|-------------| -| **Build & Test** | Every PR / push | Compile + unit tests (JUnit 5, `test` profile with H2) | -| **OWASP Dependency Check** | Every PR / push | CVE scan — **fails on CVSS ≥ 7** | -| **CodeQL Analysis** | Every PR / push | Static security analysis | -| **Package JAR** | Merge to `main` | Builds production artifact | +| Job | When | What | +|-----|------|------| +| Build & Test | Every push / PR | Compile + unit tests (H2 in-memory) | +| OWASP Dependency Check | Every push / PR | CVE scan | +| CodeQL Analysis | Every push / PR + weekly | Static security analysis | +| Package | Merge to main | Production JAR | --- -## 📚 Project Documentation +## Future plans -| Document | Description | -|----------|-------------| -| [`CONTEXT.md`](CONTEXT.md) | Single source of truth: architecture, stack, conventions, and rules | -| [`DECISIONS.md`](DECISIONS.md) | 8 Architecture Decision Records with full reasoning | -| [`SECURITY.md`](SECURITY.md) | Threat model, OWASP Top 10 (2025) full coverage, unbreakable rules | -| [`AGENTS.md`](AGENTS.md) | Instructions for AI agents working on this codebase | -| [`database/README.md`](database/README.md) | ER diagram, FK rules, DB security design decisions | -| [`CONTRIBUTING.md`](CONTRIBUTING.md) | How to contribute | -| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Community standards | +- Review and test all endpoints end-to-end +- Polish existing code sections marked for improvement +- Postman collection for easier API exploration --- -## 🔐 Security Vulnerability Reporting +## Project documentation -This is an academic/portfolio project. -If you find a vulnerability, please open an issue with the `security` label. +| Document | What it covers | +|----------|---------------| +| [`CONTEXT.md`](CONTEXT.md) | Architecture, conventions, full stack details | +| [`DECISIONS.md`](DECISIONS.md) | 8 Architecture Decision Records with reasoning | +| [`SECURITY.md`](SECURITY.md) | Threat model and security decisions in depth | +| [`database/README.md`](database/README.md) | ER diagram, foreign key rules, DB design | --- -## 📄 License +## License [MIT](LICENSE) © 2025–2026 DJAngel973 From d60b1f409e9c1f55d93b6728bc877f055c231a1b Mon Sep 17 00:00:00 2001 From: DJAngel973 <189556164+DJAngel973@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:46:51 -0500 Subject: [PATCH 2/4] feat(config): add local development scripts --- .gitignore | 1 + scripts/dev-start.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ scripts/dev-stop.sh | 28 ++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100755 scripts/dev-start.sh create mode 100755 scripts/dev-stop.sh diff --git a/.gitignore b/.gitignore index fd798d2..d82e68e 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ Thumbs.db *.truststore secrets/ credentials/ +.atl # Application configs application-local.yml diff --git a/scripts/dev-start.sh b/scripts/dev-start.sh new file mode 100755 index 0000000..67f5f56 --- /dev/null +++ b/scripts/dev-start.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# ================================================================ +# dev-start.sh — Start local development environment +# Usage: ./script/dev-start.sh +# ================================================================ + +# Move to project root regardless of where script is called from +cd "$(dirname "$0")/.." || exit 1 + +# 1. Load .env FIRST — Docker Compose needs these variables to start +# set -a exports ALL variables defined after it +# source .env reads the file +# set +a stops auto-exporting +if [ ! -f .env ]; then + echo "ERROR: .env file not found. Copy .env.example and fill in your values." + exit 1 +fi +set -a && source .env && set +a +echo "Environment variables loaded." + +# 2. Start PostgreSQL container +echo "Starting PostgreSQL..." +docker compose up -d postgres + +# 3. Wait for PostgreSQL to be healthy (uses healthcheck defined in docker-compose.yml) +echo "Waiting for PostgreSQL to be ready..." +until docker compose exec -T postgres pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" 2>/dev/null; do + sleep 1 +done +echo "PostgreSQL is ready." + +# 4. Start Spring Boot with dev profile (background + PID) +# WHY background + PID file: allows dev-stop.sh to kill only THIS process, +# not any other Java project running on the same machine. +echo "Starting Spring Boot on port ${SERVER_PORT:-8080}..." +./mvnw spring-boot:run -Dspring-boot.run.profiles=dev & +SPRING_PID=$! +echo "$SPRING_PID" > .spring-boot.pid +echo "Spring Boot PID: $SPRING_PID (saved to .spring-boot.pid)" + +# Wait keeps the script alive so you can see logs — Ctrl+C to stop +wait diff --git a/scripts/dev-stop.sh b/scripts/dev-stop.sh new file mode 100755 index 0000000..beb7135 --- /dev/null +++ b/scripts/dev-stop.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ================================================================ +# dev-stop.sh — Stop local development environment +# Usage: ./script/dev-stop.sh +# ================================================================ + +cd "$(dirname "$0")/.." || exit 1 + +# 1. Stop Spring Boot using PID file +# WHY PID file instead of pkill: isolates THIS project — won't kill other Java apps. +echo "Stopping Spring Boot..." +if [ -f .spring-boot.pid ]; then + PID=$(cat .spring-boot.pid) + if kill "$PID" 2>/dev/null; then + echo "Spring Boot stopped (PID $PID)." + else + echo "Spring Boot process (PID $PID) was not running." + fi + rm -f .spring-boot.pid +else + echo "No .spring-boot.pid found. Spring Boot may not be running." +fi + +echo "Stopping PostgreSQL container..." +docker compose stop postgres +echo "PostgreSQL stopped." + +echo "Done. All services stopped." From 3fe08c17a9fc0765ae1760ff30e4a815e4c74df1 Mon Sep 17 00:00:00 2001 From: DJAngel973 <189556164+DJAngel973@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:25:40 -0500 Subject: [PATCH 3/4] fix(config): consolidate maven and test configuration --- pom.xml | 6 ---- src/main/resources/application-test.yml | 37 ------------------------- 2 files changed, 43 deletions(-) delete mode 100644 src/main/resources/application-test.yml diff --git a/pom.xml b/pom.xml index 65abfaf..4e50da4 100644 --- a/pom.xml +++ b/pom.xml @@ -166,12 +166,6 @@ local - - - - org.springframework.boot - spring-boot-maven-plugin - org.projectlombok diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml deleted file mode 100644 index 0036832..0000000 --- a/src/main/resources/application-test.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Profile activated in CI: SPRING_PROFILES_ACTIVE=test -# Overrides application.yml for the test environment. -# Uses H2 in-memory DB — no PostgreSQL needed in CI. - -spring: - datasource: - url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - username: sa - password: - driver-class-name: org.h2.Driver - - jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: create-drop # CI: create tables fresh, drop after tests - show-sql: false - - # Disable PostgreSQL-specific settings for H2 - datasource.hikari: - maximum-pool-size: 5 - minimum-idle: 1 - -# JWT — fixed values for tests (not real secrets) -jwt: - secret: test-secret-key-minimum-32-characters-long-ok - expiration: 900000 - refresh-expiration: 604800000 - issuer: secure-wallet-api - header: Authorization - prefix: Bearer - -# Disable actuator noise in tests -management: - endpoints: - web: - exposure: - include: health \ No newline at end of file From 6410bd8f942cd084bd4bec9a10e90ff4e47b5792 Mon Sep 17 00:00:00 2001 From: DJAngel973 <189556164+DJAngel973@users.noreply.github.com> Date: Sat, 1 Aug 2026 01:05:33 -0500 Subject: [PATCH 4/4] docs: align portfolio documentation with implemented features --- .github/workflows/ci.yml | 4 ++-- README.md | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87d0c70..f95e8d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - name: Run tests run: ./mvnw test --no-transfer-progress env: - # The test use H2 (application-test.yml), they do not need PostgreSQL + # The tests use H2 (application-test.yml); they do not need PostgreSQL # Los tests usan H2 (application-test.yml), no necesitan PostgreSQL SPRING_PROFILES_ACTIVE: test @@ -52,7 +52,7 @@ jobs: # ═══════════════════════════════════════════════════════════ # JOB 2: OWASP Dependency Check # Uses official GitHub Action — no H2 race condition bug - # ══════���════════════════════════════════════════════════════ + # ═══════════════════════════════════════════════════════════ owasp-check: name: OWASP Dependency Check runs-on: ubuntu-latest diff --git a/README.md b/README.md index 5aa1d9a..9a87d9b 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ A REST API for a digital savings wallet where users can: - Register and manage their profile - Create savings wallets in multiple currencies (USD, EUR, COP, MXN, ARS) - Deposit, withdraw, and transfer funds between wallets -- Track every operation through a full audit trail -- Authenticate with dual-token JWT and optional two-factor authentication +- Track authentication, wallet, and transaction events through an audit trail +- Authenticate with access and refresh JWT tokens The idea came from wanting to understand the moving parts of a real savings wallet — not just the happy path, but edge cases, concurrency, token rotation, and what happens when things go wrong. @@ -28,7 +28,6 @@ The idea came from wanting to understand the moving parts of a real savings wall - Registration, profile updates, and soft-delete accounts - Role-based access: `USER`, `ADMIN`, `MANAGER` - Account lockout after repeated failed login attempts -- Two-Factor Authentication via TOTP (compatible with Google Authenticator, Authy) ### Multi-Currency Savings Wallets - One wallet per user per currency @@ -44,8 +43,6 @@ The idea came from wanting to understand the moving parts of a real savings wall | Transfer | Move funds between two wallets in the system | | History | State-change log per transaction | -- Configurable limits: max per transaction, max per day -- 2FA threshold: operations above a configurable amount require a TOTP code - Pessimistic locking on balance reads to prevent race conditions - Unique reference code per transaction @@ -53,7 +50,7 @@ The idea came from wanting to understand the moving parts of a real savings wall - Dual-token JWT: Access Token (short-lived) + Refresh Token (longer-lived with rotation) - Refresh token stored as SHA-256 hash — real revocation on logout - UUID primary keys on all entities (no sequential IDs) -- Stack traces never reach the client — all errors go through a centralized handler +- The default configuration prevents stack traces and internal exception details from reaching clients - Input validation on every endpoint ### Audit Trail @@ -126,13 +123,15 @@ docker compose up -d ### Run tests ```bash -./mvnw test # All unit tests (uses H2 in-memory, no DB needed) +./mvnw test # Complete automated test suite ./mvnw test -Dtest=TransactionServiceTest # Single test class ``` +The suite contains Mockito unit tests for the business services and a small Spring Security integration test using H2. PostgreSQL is not required to run the test suite. + ### Interactive API docs -Once running: [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) +Once running: [http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) All responses follow a standard wrapper: @@ -146,13 +145,13 @@ All responses follow a standard wrapper: --- -**Note:** endpoints listed in Features above have not been tested end-to-end yet. The Swagger UI at `http://localhost:8080/swagger-ui.html` lists all available endpoints for exploration when the app is running. +**Note:** endpoints listed in Features above have not been tested end-to-end yet. The Swagger UI at `http://localhost:8080/swagger-ui/index.html` lists all available endpoints for exploration when the app is running. --- ## Database -PostgreSQL 16 with schema managed through versioned SQL scripts — Hibernate validates but never creates or modifies tables. +PostgreSQL 16 with schema managed through numbered SQL scripts. The default published configuration uses `ddl-auto: validate`, so Hibernate validates the schema without creating or modifying it. | # | Script | Purpose | |---|--------|---------| @@ -190,10 +189,10 @@ This project uses the OWASP Top 10 as a guide for security decisions. Here is wh | Job | When | What | |-----|------|------| -| Build & Test | Every push / PR | Compile + unit tests (H2 in-memory) | -| OWASP Dependency Check | Every push / PR | CVE scan | -| CodeQL Analysis | Every push / PR + weekly | Static security analysis | -| Package | Merge to main | Production JAR | +| Build & Test | Push to `main` / PR targeting `main` | Compile and run the automated test suite | +| OWASP Dependency Check | Push to `main` / PR targeting `main` | Scan dependencies for known vulnerabilities | +| CodeQL Analysis | Push to `main`, PR targeting `main`, and weekly | Static security analysis | +| Package | Push to `main` | Generate and upload the application JAR | --- @@ -202,6 +201,8 @@ This project uses the OWASP Top 10 as a guide for security decisions. Here is wh - Review and test all endpoints end-to-end - Polish existing code sections marked for improvement - Postman collection for easier API exploration +- TOTP-based two-factor authentication for high-value transactions +- Enforcement of configurable per-transaction and daily limits ---