Skip to content

Repository files navigation

Secure Backend Application

This is a secure backend application built with Spring Boot, featuring JWT authentication, PostgreSQL database, and ActiveMQ messaging.

Profiles

The application supports three main profile modes:

Local Profile

The local profile uses H2 in-memory database and is suitable for development and testing.

To run with local profile:

./mvnw spring-boot:run -Dspring-boot.run.profiles=local

This profile enables demo seed data automatically.

Docker Profile

The Docker Compose setup uses PostgreSQL and includes the demo seed profile so it behaves like the lightweight local experience.

To run with Docker profile:

docker compose up --build

This starts the backend with docker,demo, so it loads the demo users, sample products, and sample orders into PostgreSQL.

Demo Seed Profile

When you need the same seeded PostgreSQL-backed experience without Docker Compose, add the demo profile on top of docker:

./mvnw spring-boot:run -Dspring-boot.run.profiles=docker,demo

Only local and demo enable startup seed data.

Public Admin Bootstrap

Public deployments should create one explicit admin account through environment-backed configuration instead of demo seed data.

Required variables:

APP_BOOTSTRAP_ADMIN_ENABLED=true
APP_BOOTSTRAP_ADMIN_USERNAME=admin
APP_BOOTSTRAP_ADMIN_PASSWORD=<16+ character secret>
APP_BOOTSTRAP_ADMIN_EMAIL=admin@example.com

When enabled, startup creates that admin if it does not already exist. If the configured username/email already belongs to a non-admin user, startup fails instead of silently mutating the account.

Public Product Bootstrap

Public deployments can also bootstrap a safe baseline product catalog without re-enabling demo users or sample orders.

Required variable:

APP_BOOTSTRAP_PRODUCTS_ENABLED=true

Optional override:

APP_BOOTSTRAP_PRODUCTS_CATALOG=classpath:bootstrap/products.json

When enabled, startup inserts the versioned catalog only when the products table is empty. If products already exist, bootstrap skips creation. This keeps clean demo resets deterministic without reintroducing local/demo seed users.

This will start:

  • Backend service on port 4001
  • Frontend service on port 8081
  • PostgreSQL database on port 5432
  • ActiveMQ on ports 61616 (broker) and 8161 (web console)

Database Access

Accessing PostgreSQL Database

You can interact with the PostgreSQL database in several ways:

  1. Using docker exec and the psql command-line tool:
# Connect to the database
docker exec -it test-secure-backend-postgres-1 psql -U postgres -d testdb

# Common psql commands:
\dt                 # List tables
\d table_name       # Describe table
\q                  # Quit psql

# Example queries:
SELECT * FROM app_user;
SELECT * FROM products;
SELECT * FROM cart_items;
SELECT * FROM orders;
  1. Using external tools:
    • Host: localhost
    • Port: 5432
    • Database: testdb
    • Username: postgres
    • Password: postgres

Database Schema

The main tables in the database:

  • app_user: Stores user information
  • email_event: Stores per-user email delivery metadata for the app-owned verification endpoint
  • products: Stores product catalog
  • cart_items: Stores shopping cart items
  • orders: Stores order information

API Documentation

The API documentation is available at:

Initial Data

Demo seed data is available only in local or docker,demo runs. Public/server deployments should keep it disabled.

  • Admin users (username/password):
    • admin/LocalDemoAdmin123!
  • Client users:
    • client/client
    • client2/client2
    • client3/client3
  • Sample products in various categories
  • Public-safe deployments can bootstrap only the product catalog through APP_BOOTSTRAP_PRODUCTS_ENABLED=true

Public deployments must not rely on these credentials.

Security

The application uses JWT tokens for authentication. To access protected endpoints:

  1. Get a token using the /api/v1/users/signin endpoint
  2. Include the token in the Authorization header: Bearer <token>

Sign in responses now include both an access token and a refresh token. The refresh token can be exchanged via POST /api/v1/users/refresh even when the access token expires, and calling POST /api/v1/users/logout revokes the refresh token on the server.

Production deployments must provide JWT_SECRET_KEY with at least 32 random bytes and set JWT_REQUIRE_SECURE_KEY=true. The application then refuses to start with a missing, short, or known development key. Rotating the key invalidates existing access tokens, so users may need to refresh their session or sign in again.

The training stack also includes an OIDC-based SSO bridge by default. The frontend authenticates with the configured identity provider and exchanges the returned OIDC ID token through POST /api/v1/users/sso/exchange. The backend validates the external token, provisions or reuses a local user, and returns the same app-issued JWT and refresh token shape as password login. Protected APIs continue to accept only the app JWT, not raw identity-provider tokens. Set APP_SSO_ENABLED=false only when you intentionally want to disable the exchange endpoint.

The local identity provider is Keycloak, started by the sibling awesome-localstack repository. Keycloak owns the SSO users and their passwords. This backend owns only the application session, local user record, app roles, refresh tokens, carts, orders, and other domain data. In other words, SSO proves who the user is; the backend still issues and validates the app's own JWT for protected API calls.

The default local SSO configuration is:

  • issuer: http://localhost:8082/realms/awesome-testing
  • audience/client id: awesome-testing-frontend
  • exchange endpoint: POST /api/v1/users/sso/exchange
  • admin console: http://localhost:8082/admin/
  • Keycloak admin login: admin / admin

The LocalStack Keycloak realm includes two training users:

  • sso-client / SsoClient123!
  • sso-admin / SsoAdmin123!

The local Keycloak client also enables direct access grants for Playwright training fixtures. That lets tests obtain an ID token over HTTP, exchange it through the backend, and start UI tests with app-issued tokens already in browser storage. This is a local training convenience, not a production recommendation.

Testing SSO Locally

The easiest local SSO check uses the sibling LocalStack repository:

cd ../awesome-localstack
docker compose -f lightweight-docker-compose.yml up

Then open http://localhost:8081/login, click Sign in with SSO, and log in through Keycloak with:

  • sso-client / SsoClient123!
  • sso-admin / SsoAdmin123!

Expected result: the browser returns to http://localhost:8081, the app stores its normal token and refreshToken, and protected pages work with the app-issued JWT.

For a curl-level negative check, an invalid ID token should be rejected:

curl -i -X POST http://localhost:4001/api/v1/users/sso/exchange \
  -H "Content-Type: application/json" \
  -H "Origin: http://localhost:8081" \
  --data '{"idToken":"not-a-real-token"}'

Expected result: 401 with {"message":"Invalid SSO token"}.

For Playwright E2E tests in ../playwright-2025, run the local Keycloak/backend/frontend stack. The specs assume SSO is enabled in the LocalStack profile:

cd ../playwright-2025
npx playwright test tests/ui/sso.live.ui.spec.ts tests/ui/sso.fixture.ui.spec.ts tests/api/sso.exchange.api.spec.ts

sso.live.ui.spec.ts drives the real browser redirect through Keycloak. uiSsoAuthFixture obtains the Keycloak ID token over HTTP, exchanges it with this backend, and injects only the resulting app token and refreshToken into local storage before the test starts.

Features

  • User authentication with JWT tokens
  • Role-based authorization (ADMIN and CLIENT roles)
  • User management (signup, signin, edit, delete)
  • Email sending functionality via ActiveMQ
  • Ollama integration for AI text generation and chat
  • Product management
  • Shopping cart functionality
  • Order management
  • Swagger/OpenAPI documentation
  • Comprehensive test coverage

Testing & Coverage

  • Run ./mvnw verify to execute the entire unit/integration suite. The build fails if line coverage drops below 40%, and JaCoCo reports are emitted to target/site/jacoco/index.html.
  • Core scenarios are covered with focused unit tests for business services (users, products, carts, orders, email), security components (token provider, filter, authentication handler, security config), and controller utilities/exception handlers.
  • Repository-specific behavior is validated with @DataJpaTest suites for OrderRepository and CartItemRepository, ensuring the custom JPQL queries behave correctly against H2.

Getting Started

Prerequisites

  • Java 25 (Temurin distribution recommended)
  • Maven Wrapper (bundled Maven 3.9.16)
  • ActiveMQ (for email functionality)

Running the Application

  1. Clone the repository
  2. Configure ActiveMQ connection in application.yml
  3. Run the application:
    mvn spring-boot:run

The application will start on http://localhost:8080

Running Tests

mvn test

API Endpoints

Authentication

  • POST /api/v1/users/signin - Authenticate user and get JWT token
  • POST /api/v1/users/signup - Register a new client user
  • POST /api/v1/users/refresh - Refresh JWT token using a refresh token
  • POST /api/v1/users/sso/exchange - Exchange a valid OIDC ID token for an app JWT and refresh token
  • POST /api/v1/users/logout - Revoke current refresh token and logout
  • POST /api/v1/users/password/forgot - Anonymous endpoint that queues a password-reset email (always responds with 202)
  • POST /api/v1/users/password/reset - Completes a reset using the emailed token and a new password (anonymous)

User Management

  • GET /api/v1/users/me - Get current user information
  • GET /api/v1/users/me/email-events - Get the authenticated user's latest email events
  • GET /api/v1/users - Get all users (ADMIN only)
  • GET /api/v1/users/{username} - Get user by username
  • PUT /api/v1/users/{username} - Update user
  • DELETE /api/v1/users/{username} - Delete user (ADMIN only)

Products

  • GET /api/v1/products - Get all products (authenticated)
  • GET /api/v1/products/{id} - Get product by ID (authenticated)
  • POST /api/v1/products - Create new product (ADMIN only)
  • PUT /api/v1/products/{id} - Update product (ADMIN only)
  • DELETE /api/v1/products/{id} - Delete product (ADMIN only)

Shopping Cart

  • GET /api/v1/cart - Get current user's cart
  • POST /api/v1/cart/items - Add item to cart
  • PUT /api/v1/cart/items/{productId} - Update item quantity
  • DELETE /api/v1/cart/items/{productId} - Remove item from cart
  • DELETE /api/v1/cart - Clear cart

Orders

  • POST /api/v1/orders - Create a new order
  • GET /api/v1/orders - Get user's orders
  • GET /api/v1/orders/{id} - Get order by ID
  • PUT /api/v1/orders/{id}/status - Update order status (ADMIN only)
  • POST /api/v1/orders/{id}/cancel - Cancel order

QR Code

  • POST /api/v1/qr/create - Generate QR code from text (authenticated)

Email

  • POST /api/v1/email - Send an email (authenticated users only)
  • GET /api/v1/users/me/email-events - Inspect the authenticated user's latest email statuses without exposing Mailhog
  • GET /api/v1/local/email/outbox (local profile only) - Inspect the in-memory email queue when running without Artemis
  • DELETE /api/v1/local/email/outbox (local profile only) - Clear the local outbox buffer for a clean test run

Public-safe email verification is now app-owned rather than Mailhog-owned. For authenticated users, the /api/v1/users/me/email-events endpoint returns only their own recent email metadata with statuses such as QUEUED, SENT_TO_SMTP_SINK, or FAILED. Because this deployment uses Mailhog as a fake inbox, the backend does not claim real-world inbox delivery; it only reports whether the message was queued and handed off to the test mail sink.

Local Password Reset Flow

When running with the local profile the backend does not connect to Artemis. Instead, every outgoing EmailDto payload is captured by the local outbox endpoint described above so that developers (or the frontend) can retrieve the latest password-reset link without needing SMTP infrastructure. Each record contains the destination, payload, and a timestamp. Clearing the outbox before a test run makes it easy to retrieve only the latest link. In Docker/localstack profiles, reset messages are dispatched through Artemis to the dedicated JMS consumer which forwards them to Mailhog.

Every email message now carries a template identifier (e.g., PASSWORD_RESET_REQUESTED) and a properties map containing contextual data such as the reset link, expiry window, and username. Downstream consumers can render their own copy using those properties while the legacy subject/message fields remain populated for backward compatibility.

Ollama Integration

The application integrates with Ollama to provide AI text generation and chat capabilities. These features are available through secure endpoints that require authentication.

Ollama Endpoints

  • POST /api/v1/ollama/generate - Generate text using Ollama models

    • Single text generation without conversation history
    • Requires authentication with ROLE_CLIENT or ROLE_ADMIN
    • Supports Server-Sent Events (SSE) for streaming responses
    • Request body:
      {
        "model": "qwen3.5:2b",
        "prompt": "Your prompt here",
        "options": {},
        "think": false
      }
  • POST /api/v1/ollama/chat - Chat with Ollama models (stateless)

    • Supports multi-message conversations with history
    • Client maintains conversation history by sending all previous messages
    • Requires authentication with ROLE_CLIENT or ROLE_ADMIN
    • Supports Server-Sent Events (SSE) for streaming responses
    • Request body:
      {
        "model": "qwen3.5:2b",
        "messages": [
          { "role": "system", "content": "You are a helpful assistant." },
          { "role": "user", "content": "Hello!" },
          { "role": "assistant", "content": "Hi there!" },
          { "role": "user", "content": "How are you?" }
        ],
        "options": {},
        "think": false
      }
  • POST /api/v1/ollama/chat/tools - Chat with Ollama models and invoke backend functions (stateless)

    • Accepts the same conversation history as /api/v1/ollama/chat plus a tools array that describes available functions

    • Streams every chunk (assistant thinking, tool calls, tool results, and final reply) so workshop participants can watch the loop in real time

    • Currently exposes two functions:

      • get_product_snapshot – anchor every SKU answer with trusted JSON (price/stock/description). Small local models can hallucinate if they guess, so this is always the first hop when you are in the product lane.
      • list_products – grab a slice of the catalog right after a snapshot so the model can compare SKUs; keep it paired with get_product_snapshot to avoid invented cross-product claims.
      • We intentionally removed the Grokipedia lane, so every function call now focuses on internal inventory data.
    • Sample request:

      {
        "model": "qwen3.5:2b",
        "messages": [
          { "role": "system", "content": "You are a helpful shopping assistant." },
          { "role": "user", "content": "How much does the Retro Console cost?" }
        ],
        "tools": [
          {
            "type": "function",
            "function": {
              "name": "get_product_snapshot",
              "description": "Return catalog metadata for a product so you can answer shopper questions accurately.",
              "parameters": {
                "type": "object",
                "properties": {
                  "productId": {
                    "type": "integer",
                    "description": "Numeric product id from the catalog."
                  },
                  "name": {
                    "type": "string",
                    "description": "Exact product name when the id is unknown."
                  }
                },
                "oneOf": [
                  { "required": ["productId"] },
                  { "required": ["name"] }
                ]
              }
            }
          }
        ]
      }
    • When the model decides to call get_product_snapshot, the backend executes ProductService, streams a role: "tool" payload containing the JSON snapshot (or { "error": "..." }), and then resubmits the expanded history back to Ollama so the final assistant reply references the real data.

  • GET /api/v1/ollama/chat/tools/definitions - Returns the JSON schema for every supported tool so SDKs/frontends can stay in sync with the backend contract (requires the same auth as the chat endpoints)

Prompt Management

Each user can configure two system prompts that the backend injects automatically:

  • GET/PUT /api/v1/users/chat-system-prompt – controls the general conversation tone for /api/v1/ollama/chat.
  • GET/PUT /api/v1/users/tool-system-prompt – explains how to use the catalog tools for /api/v1/ollama/chat/tools.

Clients can fetch these endpoints to display/edit the prompts, but they no longer need to include the strings in the messages array; the controller prepends them before relaying any request to Ollama.

Request Parameters

Both endpoints support the following parameters:

  • model (required): The Ollama model to use (e.g., "qwen3.5:2b")
  • options (optional): Model-specific options (e.g., temperature, max tokens)
  • think (optional): Set to true for 'thinking' models that benefit from reasoning before responding. Defaults to false

For the /generate endpoint:

  • prompt (required): The text prompt to generate from

For the /chat endpoint:

  • messages (required): Array of conversation messages with role and content

Configuration

The Ollama service can be configured in application.yml:

ollama:
  base-url: http://localhost:11434  # Default Ollama server URL

WebSocket Traffic Monitoring

The application includes a real-time HTTP traffic monitoring system implemented with WebSockets. This feature allows tracking and visualization of all HTTP requests in the application.

Features

  • Real-time tracking of HTTP requests and responses
  • Captures HTTP method, path, status code, response time, and timestamp
  • Events are broadcast via WebSocket to connected clients
  • Secured access requiring authentication

Architecture

  • Traffic is captured using a servlet filter (TrafficLoggingFilter)
  • Events are stored in a thread-safe concurrent queue
  • A scheduled publisher broadcasts events to WebSocket subscribers
  • Uses STOMP protocol over WebSocket for messaging

WebSocket Endpoints

  • WebSocket Connection: /api/v1/ws-traffic
  • Subscription Topic: /topic/traffic
  • Data Format:
    {
      "method": "GET",
      "path": "/api/v1/products",
      "status": 200,
      "durationMs": 45,
      "timestamp": "2023-03-22T10:15:30.123Z"
    }

Usage

  1. Connect to the WebSocket endpoint:

    const socket = new SockJS('/api/v1/ws-traffic');
    const stompClient = Stomp.over(socket);
    
    // Include JWT token for authentication
    const headers = {
      'Authorization': 'Bearer ' + jwtToken
    };
    
    stompClient.connect(headers, function(frame) {
      // Subscribe to traffic events
      stompClient.subscribe('/topic/traffic', function(message) {
        const trafficEvent = JSON.parse(message.body);
        console.log('New traffic event:', trafficEvent);
        // Handle event (e.g., update UI)
      });
    });
  2. Traffic events are automatically captured and broadcast as they occur in the application

Visualization Tool

A simple HTML-based visualization tool is provided to help monitor traffic events in real-time:

  1. Run the web server on port 8081 (this is CORS requirement, my frontend uses the same port - see WebSecurityConfig). Use absolute path
jwebserver -p 8081 -d /Users/slawek/IdeaProjects/test-secure-backend
  1. Go to http://localhost:8081/traffic-monitor.html
  2. Enter your server URL (default: http://localhost:4001)
  3. Paste a valid JWT token (obtained from /api/v1/users/signin endpoint)
  4. Click "Connect" to establish the WebSocket connection
  5. Watch as HTTP traffic events appear in real-time

API Endpoints

  • GET /api/v1/traffic/info - Get WebSocket connection information (authenticated)
    • Returns:
      {
        "endpoint": "/api/v1/ws-traffic",
        "topic": "/topic/traffic",
        "description": "WebSocket endpoint for real-time HTTP traffic events"
      }

Test Strategy

The application follows a comprehensive testing strategy focusing on endpoint-level integration tests. Key aspects include:

  1. Independent Endpoint Testing

    • Each endpoint is tested in isolation
    • Tests are organized by feature in separate packages
    • All possible HTTP response codes are tested for each endpoint
  2. Test Organization

    • Tests are ordered by HTTP status code (2xx first, then 4xx, 5xx)
    • Each test class focuses on a single endpoint functionality
    • Example test coverage for an endpoint:
      • 200/201 - Successful operations
      • 400 - Bad Request (invalid input)
      • 401 - Unauthorized (no authentication)
      • 403 - Forbidden (insufficient permissions)
      • 404 - Not Found (resource doesn't exist)
  3. Test Data Generation

    • Uses factory pattern for test data creation
    • Factories generate random, valid test data using Faker
    • Located in test/factory package
  4. Test Structure

    • Given/When/Then format for clear test organization
    • Descriptive test method names indicating the scenario
    • Comprehensive assertions for response status and body

Example test class organization:

class SomeEndpointTest {
    void shouldReturnSuccessfully(); // 200 OK

    void shouldCreate(); // 201 Created

    void shouldGet400WhenInvalidInput();

    void shouldGet401WhenNoAuthorizationHeader();

    void shouldGet403WhenNotAuthorized();

    void shouldGet404WhenNotFound();
}

AI Debugging Tips

When working with AI assistants, keep in mind:

  1. Test failures may be caused by recent changes since git HEAD is kept stable. To see the changes use:

    git --no-pager diff
  2. To run a single test and save the output to the testlogs folder, use JUnit notation:

    mvn test -Dtest=TestClassName#testMethodName > ./testlogs/test-output.log

    This helps in analyzing test failures by providing detailed logs.

  3. The test logs can be read and analyzed by AI to help diagnose issues.

  4. When making changes, always verify that all tests pass using:

    mvn test

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages