Component Integration - ericfitz/tmi GitHub Wiki
Component Integration
This guide covers integrating all TMI components to create a complete working system.
Overview
A fully operational TMI deployment consists of:
- TMI Web Application (Frontend)
- TMI Server (API Backend)
- PostgreSQL (Database)
- Redis (Cache)
- OAuth Providers (Authentication)
- Load Balancer/Reverse Proxy (Optional, recommended for production)
Integration Architecture
βββββββββββββββββββ
β User Browser β
ββββββββββ¬βββββββββ
β HTTPS
β
βββββββββββββββββββ
β Load Balancer β (Optional)
β / Reverse β
β Proxy β
ββββββ¬βββββββ¬ββββββ
β β
β βββββββββββββββ
β HTTPS β HTTPS
β β
ββββββββββββββββ ββββββββββββββββββ
β TMI Web App β β TMI Server β
β (Static) β β (API/WebSoc...β
ββββββββββββββββ βββββ¬βββββββββ¬ββββ
β β
β TCP β TCP
β β
ββββββββββββ ββββββββββββ
βPostgreSQLβ β Redis β
ββββββββββββ ββββββββββββ
β
ββββββββββββββββ
βOAuth Providerβ
β(Google/GitHubβ
β /Microsoft) β
ββββββββββββββββ
Prerequisites
Before integration, ensure you have:
- TMI server deployed and running
- TMI web application built and deployed
- PostgreSQL configured with migrations run
- Redis configured and running
- OAuth providers configured
- DNS records configured (for production)
- SSL/TLS certificates (for production)
Integration Steps
Step 1: Configure API Connection
The web application needs to know where to find the API server.
Web Application Configuration
Edit src/environments/environment.prod.ts:
export const environment: Environment = {
production: true,
logLevel: 'ERROR',
apiUrl: 'https://api.tmi.example.com/v1', // Your TMI API server URL
authTokenExpiryMinutes: 60,
operatorName: 'TMI Operator',
operatorContact: '[email protected]',
operatorJurisdiction: '',
securityConfig: {
enableHSTS: true,
hstsMaxAge: 31536000,
hstsIncludeSubDomains: true,
hstsPreload: false,
frameOptions: 'DENY',
referrerPolicy: 'strict-origin-when-cross-origin',
permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
},
};
The apiUrl can also be overridden at runtime via the TMI_API_URL environment variable when running the containerized web application.
Important: The apiUrl must be accessible from users' browsers. Options:
-
Separate subdomain (recommended):
- Web app:
https://tmi.example.com - API:
https://api.tmi.example.com
- Web app:
-
Same domain, different path:
- Web app:
https://tmi.example.com - API:
https://tmi.example.com/api - Requires reverse proxy to route
/apito TMI server
- Web app:
-
Development:
- Web app:
http://localhost:4200 - API:
http://localhost:8080
- Web app:
CORS Configuration
If web app and API are on different domains, configure CORS in TMI server.
Environment Variable (for general HTTP CORS):
TMI_CORS_ALLOWED_ORIGINS="https://tmi.example.com,https://www.tmi.example.com"
Environment Variable (for WebSocket origin checking, separate from HTTP CORS):
WEBSOCKET_ALLOWED_ORIGINS="https://tmi.example.com,https://www.tmi.example.com"
Configuration File (config-production.yml):
server:
cors:
# Env: TMI_CORS_ALLOWED_ORIGINS (comma-separated)
allowed_origins:
- "https://tmi.example.com"
- "https://www.tmi.example.com"
Note: TMI_CORS_ALLOWED_ORIGINS controls HTTP CORS headers for REST API requests. WEBSOCKET_ALLOWED_ORIGINS is a separate setting that controls which origins may establish WebSocket connections. In production, both should typically be set to the same value(s).
Step 2: Configure OAuth Flow
OAuth requires coordination between web app, TMI server, and OAuth providers.
OAuth Redirect URIs
Configure redirect URIs in OAuth provider dashboards:
Web Application Callback: https://tmi.example.com/oauth2/callback
This is where users land after authenticating with the provider.
TMI Server OAuth Configuration
Configure the OAuth callback URL that TMI server uses:
Configuration File (config-production.yml):
auth:
oauth:
callback_url: "https://api.tmi.example.com/oauth2/callback"
providers:
google:
enabled: true
client_id: "" # Set via env var OAUTH_PROVIDERS_GOOGLE_CLIENT_ID
client_secret: "" # Set via env var OAUTH_PROVIDERS_GOOGLE_CLIENT_SECRET
github:
enabled: true
client_id: "" # Set via env var OAUTH_PROVIDERS_GITHUB_CLIENT_ID
client_secret: "" # Set via env var OAUTH_PROVIDERS_GITHUB_CLIENT_SECRET
Important: OAuth client IDs and secrets should be set via environment variables (e.g., OAUTH_PROVIDERS_GOOGLE_CLIENT_ID), not committed in configuration files. The web app handles the user-facing callback, then sends the authorization code to the TMI server's token exchange endpoint.
OAuth Flow Integration
The complete OAuth flow:
- User clicks login β Web app redirects to OAuth provider
- User authenticates β Provider redirects to web app callback
- Web app receives code β Sends code to TMI server
- TMI server exchanges code β Gets user info from provider
- TMI server issues JWT β Returns tokens to web app
- Web app stores tokens β Uses for subsequent API requests
Step 3: Database Integration
Ensure TMI server can connect to PostgreSQL and Redis.
PostgreSQL Connection
Test connection from TMI server:
# From TMI server host
psql -h postgres-host -U tmi_user -d tmi -c "SELECT version();"
Configure TMI server (connection URL, required):
TMI_DATABASE_URL="postgres://tmi_user:[email protected]:5432/tmi?sslmode=require"
The TMI_DATABASE_URL environment variable is required and uses the standard connection URL format. The URL scheme determines the database type automatically. Supported schemes include postgres://, mysql://, sqlserver://, sqlite:///, and oracle://.
Verify migrations:
# Check database schema
make check-database
# Should show all tables present
Redis Connection
Test connection from TMI server:
# From TMI server host
redis-cli -h redis-host -p 6379 -a redis_password ping
# Expected: PONG
Configure TMI server (connection URL):
TMI_REDIS_URL="redis://[email protected]:6379/0"
Alternatively, configure individual fields in config-production.yml:
database:
redis:
host: "redis.example.com"
port: "6379"
password: "redis_password"
db: 0
If TMI_REDIS_URL is set, it takes precedence over individual fields.
Network Configuration
Ensure TMI server can reach databases:
# Test PostgreSQL connectivity
telnet postgres.example.com 5432
# Test Redis connectivity
telnet redis.example.com 6379
Firewall rules:
- PostgreSQL: Allow only from TMI server IPs
- Redis: Allow only from TMI server IPs
- Do not expose databases to internet
Step 4: Configure Reverse Proxy
A reverse proxy provides SSL termination, load balancing, and routing.
Nginx Configuration
Create /etc/nginx/sites-available/tmi:
# Upstream servers
upstream tmi_api {
server localhost:8080;
# Add more servers for load balancing
# server 10.0.0.11:8080;
# server 10.0.0.12:8080;
keepalive 32;
}
# Web application
server {
listen 443 ssl http2;
server_name tmi.example.com;
ssl_certificate /etc/letsencrypt/live/tmi.example.com/fullchain.pem;
ssl_private_key /etc/letsencrypt/live/tmi.example.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Web application (static files)
root /var/www/tmi-ux;
index index.html;
# Enable gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
# Static assets with long cache
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Angular routing
location / {
try_files $uri $uri/ /index.html;
}
}
# API server
server {
listen 443 ssl http2;
server_name api.tmi.example.com;
ssl_certificate /etc/letsencrypt/live/api.tmi.example.com/fullchain.pem;
ssl_private_key /etc/letsencrypt/live/api.tmi.example.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://tmi_api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts for WebSocket
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name tmi.example.com api.tmi.example.com;
return 301 https://$server_name$request_uri;
}
Enable configuration:
sudo ln -s /etc/nginx/sites-available/tmi /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Alternative: Combined Domain with Path Routing
For same-domain deployment:
server {
listen 443 ssl http2;
server_name tmi.example.com;
ssl_certificate /etc/letsencrypt/live/tmi.example.com/fullchain.pem;
ssl_private_key /etc/letsencrypt/live/tmi.example.com/privkey.pem;
# API routes
location /api/ {
rewrite ^/api/(.*) /$1 break;
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Web application
root /var/www/tmi-ux;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Update web app configuration:
apiUrl: 'https://tmi.example.com/api'
Step 5: SSL/TLS Configuration
Let's Encrypt with Certbot
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Get certificate for web app
sudo certbot --nginx -d tmi.example.com -d www.tmi.example.com
# Get certificate for API
sudo certbot --nginx -d api.tmi.example.com
# Test renewal
sudo certbot renew --dry-run
Certbot automatically:
- Obtains certificates
- Configures nginx
- Sets up auto-renewal
Manual Certificate Installation
If using custom certificates:
# Copy certificates
sudo cp tmi.example.com.crt /etc/ssl/certs/
sudo cp tmi.example.com.key /etc/ssl/private/
sudo chmod 600 /etc/ssl/private/tmi.example.com.key
Configure in nginx (shown in Step 4).
Step 6: DNS Configuration
Configure DNS records for your domains:
A Records
tmi.example.com A 203.0.113.10
api.tmi.example.com A 203.0.113.10
Or use separate IPs:
tmi.example.com A 203.0.113.10 (web app server)
api.tmi.example.com A 203.0.113.11 (API server)
CNAME Records (Alternative)
tmi.example.com A 203.0.113.10
www.tmi.example.com CNAME tmi.example.com
api.tmi.example.com CNAME tmi.example.com
Verify DNS
# Check DNS resolution
dig tmi.example.com
dig api.tmi.example.com
# Or with nslookup
nslookup tmi.example.com
nslookup api.tmi.example.com
Testing Integration
Step 1: Test API Server
# Health/info check (root endpoint)
curl https://api.tmi.example.com/
# Expected response (JSON when called without browser Accept header):
{
"status": {
"code": "OK",
"time": "2025-11-12T12:00:00Z"
},
"service": {
"build": "1.4.0-abc1234 (production)",
"name": "TMI"
},
"api": {
"specification": "https://github.com/ericfitz/tmi/blob/main/api-schema/tmi-openapi.json",
"version": "1.4.0"
}
}
# Check OAuth providers
curl https://api.tmi.example.com/oauth2/providers
# Expected response:
{
"providers": [
{"id": "google", "name": "Google", "icon": "fa-brands fa-google"},
{"id": "github", "name": "GitHub", "icon": "fa-brands fa-github"}
]
}
Note: The root endpoint (/) serves as both the health check and API info endpoint. When accessed from a browser (with Accept: text/html), it returns an HTML page. When the system is degraded, the response includes a health object with database and Redis status details.
Step 2: Test Web Application
# Check if web app loads
curl -I https://tmi.example.com
# Should return 200 OK
# Check static assets
curl -I https://tmi.example.com/main.js
# Should return 200 OK with long cache headers
Step 3: Test OAuth Flow
- Open web app in browser:
https://tmi.example.com - Click login button
- Select OAuth provider (Google/GitHub/Microsoft)
- Complete authentication
- Verify redirect to application
- Check browser console for errors
- Verify JWT token received
Step 4: Test API Integration
After logging in:
// In browser console
fetch('https://api.tmi.example.com/threat_models', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('tmi_access_token')
}
})
.then(r => r.json())
.then(console.log)
Should return threat models (empty array if none created yet).
Step 5: Test WebSocket Connection
TMI uses ticket-based authentication for WebSocket connections. The flow is:
- Obtain a ticket via an authenticated REST call:
// In browser console (after login)
const sessionId = crypto.randomUUID();
const response = await fetch(
`https://api.tmi.example.com/ws/ticket?session_id=${sessionId}`,
{ headers: { 'Authorization': 'Bearer ' + localStorage.getItem('tmi_access_token') } }
);
const { ticket } = await response.json();
- Connect to WebSocket using the ticket:
const ws = new WebSocket(
`wss://api.tmi.example.com/ws/diagrams/${diagramId}?ticket=${ticket}&session_id=${sessionId}`
);
ws.onopen = () => console.log('WebSocket connected');
ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = () => console.log('WebSocket closed');
Note: Tickets are single-use and expire after 30 seconds. The GET /ws/ticket endpoint requires a valid JWT (Bearer token or session cookie). The WebSocket connection itself authenticates via the ?ticket= query parameter, not via headers.
Common Integration Issues
Issue 1: CORS Errors
Symptom: Browser console shows CORS errors when calling API.
Solution:
- Verify
TMI_CORS_ALLOWED_ORIGINSincludes web app domain - Check API server CORS configuration
- Ensure preflight requests are handled
Test CORS:
curl -H "Origin: https://tmi.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization" \
-X OPTIONS \
https://api.tmi.example.com/threat_models
Issue 2: OAuth Redirect Loop
Symptom: After OAuth login, app redirects back to provider repeatedly.
Causes:
- Redirect URI mismatch in OAuth provider configuration
- State parameter validation failing
- Token exchange failing
Debug:
- Check browser network tab for failed requests
- Verify OAuth redirect URI matches exactly
- Check TMI server logs for token exchange errors
- Verify OAuth client credentials are correct
Issue 3: WebSocket Connection Fails
Symptom: Real-time collaboration doesn't work.
Solutions:
- Check reverse proxy WebSocket configuration
- Verify
UpgradeandConnectionheaders are forwarded - Check WebSocket allowed origins
- Increase proxy timeouts for long-lived connections
Test WebSocket:
# First obtain a ticket (requires a valid JWT)
TICKET=$(curl -s -H "Authorization: Bearer YOUR_TOKEN" \
"https://api.tmi.example.com/ws/ticket?session_id=test-session-id" | jq -r '.ticket')
# Then connect using the ticket (requires wscat)
npm install -g wscat
wscat -c "wss://api.tmi.example.com/ws/diagrams/test-id?ticket=${TICKET}&session_id=test-session-id"
Note: The ticket expires after 30 seconds and is single-use, so obtain it immediately before connecting.
Issue 4: Database Connection Timeouts
Symptom: API requests fail with database timeout errors.
Solutions:
- Check database server is running
- Verify firewall allows TMI server to database
- Check connection pool settings
- Verify database credentials
Test database connection:
# From TMI server host
psql -h postgres-host -U tmi_user -d tmi -c "SELECT 1;"
Issue 5: Static Assets Not Loading
Symptom: Web app loads but shows blank page, missing CSS/JS.
Solutions:
- Check nginx static file configuration
- Verify file permissions on /var/www/tmi-ux
- Check browser console for 404 errors
- Verify nginx is serving correct directory
Debug:
# Check files exist
ls -la /var/www/tmi-ux/
# Check nginx error log
sudo tail -f /var/log/nginx/error.log
# Test static file
curl -I https://tmi.example.com/main.js
Security Checklist
After integration, verify security:
- All traffic uses HTTPS
- HTTP redirects to HTTPS
- Security headers configured (CSP, X-Frame-Options, etc.)
- Database not accessible from internet
- Redis not accessible from internet
- OAuth secrets in environment variables, not config files
- JWT secret is strong and unique
- Rate limiting configured
- CORS properly restricted
- Firewall rules limiting access
- SSL certificates valid and auto-renewing
Performance Optimization
Connection Keep-Alive
Configure nginx:
upstream tmi_api {
server localhost:8080;
keepalive 32; # Keep connections alive
}
location / {
proxy_http_version 1.1;
proxy_set_header Connection ""; # Clear connection header
}
Caching
Configure caching headers:
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Don't cache API responses
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
Compression
Enable compression in nginx:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
Monitoring Integration
Set up monitoring for integrated system:
Health Check Endpoints
Create /usr/local/bin/check-tmi-health.sh:
#!/bin/bash
# Check web app
if ! curl -f -s https://tmi.example.com > /dev/null; then
echo "ERROR: Web app not responding"
exit 1
fi
# Check API (root endpoint serves as health check)
if ! curl -f -s https://api.tmi.example.com/ > /dev/null; then
echo "ERROR: API not responding"
exit 1
fi
# Check database
if ! psql -h postgres-host -U tmi_user -d tmi -c "SELECT 1;" > /dev/null 2>&1; then
echo "ERROR: Database not responding"
exit 1
fi
# Check Redis
if ! redis-cli -h redis-host -a password ping > /dev/null 2>&1; then
echo "ERROR: Redis not responding"
exit 1
fi
echo "OK: All components healthy"
exit 0
Monitoring Stack
Consider deploying:
- Prometheus: Metrics collection
- Grafana: Dashboards and visualization
- Loki: Log aggregation
- Alertmanager: Alert routing
Deployment Checklist
Before going live:
Pre-Production
- All components tested individually
- Integration tests pass
- OAuth flow works end-to-end
- WebSocket connections successful
- Database migrations complete
- Redis cache functioning
Production
- DNS records configured and propagated
- SSL certificates installed and valid
- Reverse proxy configured and tested
- Firewall rules in place
- Monitoring and alerting active
- Backup procedures tested
- Rollback plan documented
Verification
- Can access web app via domain
- Can login with OAuth providers
- Can create threat model
- Can edit diagrams
- Real-time collaboration works
- No errors in browser console
- No errors in server logs
Next Steps
- Post-Deployment - Final verification and testing
- Monitoring-and-Health - Set up ongoing monitoring
- Security-Best-Practices - Additional hardening