Skip to content

Linux Workstation with Docker

For local development and fast debugging, without pushing changes to the version control and waiting for the build, it is possible to install a local development environment in a laptop or a workstation.
The development workstation should be powerful enough to run PostgreSQL database, Docker containers, web server, the Java IDE and the running application (advised is a 12-core CPU with min 32GB RAM.)
To be completely independent, it is possible also to run a local copy of Odoo 17 CRM/ERP.

In the further guide, we will assume that - The development machine is named tqadm-dev.vanevski.net and the /etc/hosts file has a pointer to that name with either the real IP address of the machine, or 127.0.0.1 - The communication is HTTP (otherwise certificates and different NGINX setup are required). - The database is installed and configured / imported - Minimum of Java 17 SDK is available (tested on Amazon Corretto 17)

IDE installation

For the IDE, most convenient is to use JetBrains IntelliJ Idea; however, any IDE with Gradle support and ability to run/debug Java will suffice.

Database

PostgreSQL server 13 or later is recommended. The database tlinq and respective users should be created and imported.

User Authentication

For user authentication and authorization, we use Keycloak and Oauth2-Proxy. For local development, it is most convenient that they are installed as Docker containers. The container will be configured to expose the keycloak port on 8081 and oauth2-proxy port on standard port 4180.

Installation

To install and run the containers, it is enough to install Docker on the development machine; most convenient would be to use Docker Desktop (as it is multiplatform). You need to create the docker compose file as well as the oauth2-proxy configuration file:

  • Docker compose file (docker-compose.yaml)

    version: '3.8'
    
    services:
      keycloak:
        image: quay.io/keycloak/keycloak
        container_name: keycloak
        command: [ 'start-dev', '--hostname=http://tqadm-dev.vanevski.net', '--hostname-strict=false' ]
        environment:
          KEYCLOAK_ADMIN: admin
          KEYCLOAK_ADMIN_PASSWORD: admin
        # expose the internal 8080 on host 8081 to avoid clashes
        ports:
          - "8081:8080"
        networks:
          - authnet
    
      oauth2-proxy:
        image: quay.io/oauth2-proxy/oauth2-proxy
        container_name: oauth2-proxy-adm
        depends_on:
          - keycloak
        volumes:
          - ./oauth2-proxy-adm.cfg:/etc/oauth2-proxy.cfg:ro
        ports:
          - "4180:4180"
        command: [ "--config", "/etc/oauth2-proxy.cfg" ]
        networks:
          - authnet
    
    networks:
      authnet:
        driver: bridge
    

  • Configuration file for oauth-proxy (oauth2-proxy-cfg.adm) which is externally bound to the container and not injected in it:

    # OIDC provider: Keycloak
    provider = "keycloak-oidc"
    
    # URL to the OIDC realm. If working under HTTPS, change this url to https://
    oidc_issuer_url = "http://tqadm-dev.vanevski.net/realms/tqpro-adm" 
    
    # Must match client created in Keycloak
    client_id = "tqweb-adm"                                             
    
    # Copy from Keycloak → Credentials
    client_secret = "AXRZyCfifIwC5yd1KBi1zZ8QMT9ZQ2bU"                  
    
    # Where oauth2-proxy listens (all addresses)
    http_address = "0.0.0.0:4180"
    
    # Redirect URL after login (your frontend app)
    redirect_url = "http://tqadm-dev.vanevski.net/oauth2/callback"
    
    # Add whitelist domains for redirects
    whitelist_domains = ["tqadm-dev.vanevski.net"]
    
    # Skip authentication for sign_out endpoint (optional, if needed)
    skip_auth_routes = [
      "^/oauth2/sign_out"
    ]
    
    # Session cookie settings
    cookie_secret = "U0ui7gdP2AyXRgsRWVCke5Lm7asK0xQZEjWCZKOk8wg="  # use: `openssl rand -base64 32`
    cookie_secure = false    # must be true for HTTPS
    cookie_samesite = "lax"
    cookie_domains = ["tqadm-dev.vanevski.net"]
    set_authorization_header = true
    
    # Email domain filtering (allow all for now)
    email_domains = ["*"]
    
    # Session duration and refresh
    cookie_expire = "8h"
    cookie_refresh = "1h"
    
    # Upstream destination (local web app)
    # upstreams = ["http://127.0.0.1:8081/"]  # optional if handled fully in NGINX
    
    # Disable automatic sign-in redirect (optional)
    skip_provider_button = true
    set_xauthrequest = true
    pass_user_headers = true
    pass_access_token = true
    
    # Logging (optional)
    standard_logging = true
    auth_logging = true
    request_logging = true
    

Once these two files are created, run Docker compose and start the containers.

docker compose pull
docker compose up -d

Configuration

Once keycloak is installed, following configuration needs to be applied: - Create realm tqpro-adm - Configure the endpoints (URLs) - Create client tqweb-adm - Create two roles - agent and admin - Create the user(s) and assign corresponding roles

After that, for the TQPro API server authentication and authorization to be functional, you also need to configure in tlinq-api.properties file:

#####
# Locations for keycloak/oauth2-Proxy  authentication
auth-server=http://tqadm-dev.vanevski.net
redir-server=http://tqadm-dev.vanevski.net

Nginx configuration

For Nginx to serve the pages properly with authentication, we need to place correct locations in the file. Following locations are important:

  • Internal location /oauth2/auth where nginx will proxy all authentication requests:
      # Authentication endpoint
      location /oauth2/auth {
        internal;
        proxy_pass http://oauth2_proxy;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      }
    
  • Public endpoints of oauth2-proxy:
      location /oauth2/ {
        proxy_pass http://oauth2_proxy;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffers 16 64k;
        proxy_buffer_size 128k;
      }
    
  • Location where oauth2-proxy will forward unauthorized users to sign in:
      location @oauth2_signin {
        # Make sure this matches your issuer hostname exactly
        return 302 /oauth2/start?rd=$request_uri;
      }
    
  • Website pages that need to be protected (in our case, it is the whole website):
      # Protect / pages
      # For these pages, nginx will always send an authentication request to oauth2-proxy
      # if the proxy returns 401 (unauthorized), user will be redirected to the sign-in page
      location / {
        auth_request        /oauth2/auth;
        error_page          401 = @oauth2_signin;
        root   /var/www/nginx/html-adm;
        index  index.html;
        try_files $uri $uri/ =404;
      }
    
  • Keycloak endpoints
      location /realms/ {
        proxy_pass http://keycloak;
        proxy_set_header  Host  $host;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
      }
    
  • Sign-Out page (if needed; in TQPro we manage sign-out with an internal API)
      location /oauth2/sign_out {
        proxy_pass http://oauth2_proxy;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Auth-Request-Redirect $scheme://$host/;
      }
    
  • Pages that do not need protection:
      location /loggedout.html {
        root /var/www/nginx/html-adm;
      }
    
      location /img/ {
        root /var/www/nginx/html-adm;
      }
    
  • The API server (note the header pass-through - these headers are required for authorization for TQPro APIs)
      location /tlinq-api/ {
        auth_request /oauth2/auth;
        # capture upstream headers
        auth_request_set $user    $upstream_http_x_auth_request_user;
        auth_request_set $roles   $upstream_http_x_auth_request_groups;
        auth_request_set $email   $upstream_http_x_auth_request_email;
        auth_request_set $token   $upstream_http_x_auth_request_access_token;
        auth_request_set $uname   $upstream_http_x_auth_request_preferred_username;
        auth_request_set $idtok   $upstream_http_authorization;
        # forward to Jetty
        proxy_set_header X-User  $user;
        proxy_set_header X-Roles $roles;
        proxy_set_header X-Email $email;
        proxy_set_header X-Name  $uname;
        proxy_set_header Authorization $idtok;
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT" always;
        add_header Access-Control-Max-Age "1000" always;
        add_header Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token" always;
        proxy_pass http://tqapi_backend_adm;
      }
    
  • Finally, the upstream definitions of the servers:
    upstream keycloak {
        server tqadm-dev.vanevski.net:8081;
    }
    
    upstream tqapi_backend_adm {
        server tqadm-dev.vanevski.net:11080;
    }
    
    upstream oauth2_proxy {
        server 127.0.0.1:4180;
    }