Skip to main content

AI Chatbot Platform

Not embedded here

The widget used to load on every page of this site. It was removed: a third-party script with full DOM access on forty content pages is not something I want to defend on a site about securing delivery pipelines, and this site does not sell anything. The platform itself is no longer hosted.

Executive Summary​

This is a production-ready, multi-tenant platform for embedding customizable AI chatbots with appointment booking capabilities into e-commerce websites. Built with a modern fullstack architecture emphasizing security, scalability, and DevSecOps best practices.

Tech Stack: Next.js 13, React 18, TypeScript, Prisma ORM, PostgreSQL 17, OpenAI GPT-4, Docker, Node.js 20

Architecture Overview​

The platform consists of client websites embedding a widget via script tag, a Next.js server handling API routes, and a PostgreSQL database. External services include OpenAI API and SMTP for emails.

High-Level Architecture​

┌─────────────────────────────────────────────────────────────┐
│ Client Websites │
│ (Multiple domains embedding widget via <script> tag) │
└─────────────┬───────────────────────────────────────────────┘
│
│ HTTPS (CORS-protected)
▼
┌─────────────────────────────────────────────────────────────┐
│ Next.js Application Server │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ API Routes (/api/*) │ │
│ │ • Bot Configuration Management │ │
│ │ • Chat Interface (OpenAI Integration) │ │
│ │ • Appointment Booking System │ │
│ │ • Authentication & Authorization │ │
│ │ • Password Reset Flow │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Widget Distribution (/dist/widget.iife.js) │ │
│ │ • Shadow DOM isolated React component │ │
│ │ • Self-contained styling │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────┬────────────────────────────────────────────────┘
│
│ Prisma ORM
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL 17 Database │
│ • Bot configurations (multi-tenant) │
│ • Conversation history │
│ • Appointment schedules │
│ • Password reset tokens (bcrypt hashed) │
└─────────────────────────────────────────────────────────────┘

External Services
┌──────────────────┐
│ OpenAI API │
│ (GPT-4.1-mini) │
└──────────────────┘
┌──────────────────┐
│ SMTP Server │
│ (Nodemailer) │
└──────────────────┘

Technology Stack​

Frontend​

TechnologyVersionPurpose
React18.2.0Widget UI framework with hooks-based state management
TypeScript5.8.3Type safety across frontend and API layers
Vite7.0.0Widget bundler with IIFE output for script tag embedding
TailwindCSS3.3.2Utility-first CSS framework (build-time only)
Shadow DOMNativeCSS/JS isolation for widget embedding

Backend​

TechnologyVersionPurpose
Next.js13.4.12Full-stack React framework with API routes
Node.js20Server runtime (LTS version)
Prisma6.11.1Type-safe ORM with migration system
PostgreSQL17.5Primary relational database
OpenAI SDK4.18.0GPT-4 integration for conversational AI

Security & DevSecOps​

TechnologyVersionPurpose
bcryptjs3.0.3Password hashing (12 rounds, salted)
jsonwebtoken9.0.2JWT token generation for session management
Nodemailer7.0.4Secure email delivery (password resets, appointments)
CORSBuilt-inOrigin validation per bot configuration

Infrastructure​

TechnologyVersionPurpose
DockerLatestContainerization for consistent deployments
Docker Composev2Multi-container orchestration
PostgreSQL17.5Database container with health checks
Bash Scripts-Automated database backups

Security Architecture (DevSecOps)​

Authentication & Authorization​

Password Security​

Features:

  • ✅ 12-round bcrypt hashing for all passwords
  • ✅ Automatic hashing on bot creation and updates
  • ✅ Secure password reset flow with time-limited tokens (1 hour expiry)
  • ✅ Rate limiting: Max 3 reset requests per hour per user
  • ✅ Password policy enforcement: Min 8 chars, 1 uppercase, 1 number

Password Reset Flow​

┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Request │ │ Validate │ │ Send Email │
│ Reset │─────▶│ & Create │─────▶│ with Token │
│ │ │ Token │ │ (1h expiry) │
└─────────────┘ └──────────────┘ └──────────────┘
│
│ Store bcrypt-hashed token
▼
┌──────────────────┐
│ Database │
│ (PasswordReset) │
└──────────────────┘

User clicks link → Validate token → Update password (bcrypt) → Delete all tokens

CORS & Origin Validation​

Security Features:

  • ✅ Per-bot domain whitelisting (stored in BotConfig.allowedHost)
  • ✅ Strict origin validation on all API endpoints
  • ✅ Preflight request handling for complex CORS scenarios
  • ✅ Widget validates origin server-side before rendering
  • ✅ Cache-Control and Pragma headers allowed for fresh data fetches

Data Protection​

Sensitive Data Handling:

// Never expose passwords in API responses
select: {
id: true,
email: true,
// password: false (excluded)
}

// Environment variable isolation
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const JWT_SECRET = process.env.JWT_SECRET;
const EMAIL_PASS = process.env.EMAIL_PASS;

Email Security:

  • ✅ SMTP authentication with secure credentials
  • ✅ TLS encryption for email transmission (port 587)
  • ✅ Confirmation emails for all critical actions
  • ✅ No sensitive data in email subjects or metadata

DevSecOps Best Practices​

Container Security:

# Multi-stage builds to minimize attack surface
FROM node:20 AS widget-builder
# ... build widget

FROM node:20 AS nextjs-builder
# ... build application

FROM node:20 AS runner
# Only copy production artifacts
COPY --from=nextjs-builder /app/.next ./.next
COPY --from=nextjs-builder /app/node_modules ./node_modules

Security Checklist:

  • ✅ No hardcoded secrets (all via environment variables)
  • ✅ Prisma migrations run automatically on container start
  • ✅ Health checks for database availability
  • ✅ PostgreSQL exposed only to localhost (127.0.0.1)
  • ✅ Regular automated backups with timestamped SQL dumps
  • ✅ Rate limiting on password reset endpoints
  • ✅ Token expiration enforced on all JWTs and reset links

Database Design​

Prisma Schema Overview​

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
}

Entity Relationship Diagram​

┌─────────────────────────────────────────────────┐
│ BotConfig (Tenant) │
├─────────────────────────────────────────────────┤
│ id: String (CUID) PK │
│ client: String (Unique) │
│ name: String │
│ password: String (bcrypt hashed) │
│ email: String │
│ allowedHost: String (comma-separated origins) │
│ confirmed: Boolean │
│ appointmentsEnabled: Boolean │
│ position: String (bottom-right/left/center) │
│ ... UI configuration fields ... │
└──┬──────────────────────────────────────────────┘
│
├─────────────────┬──────────────────┬──────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐
│ Message │ │ Appointment │ │ Password │ │ (Future) │
│ │ │ Date │ │ Reset │ │ │
├──────────┤ ├───────────────┤ ├──────────────┤ └──────────────┘
│ id: PK │ │ id: PK │ │ id: PK │
│ content │ │ date: String │ │ resetToken │
│ role │ │ time: String │ │ expiresAt │
│ userId │ │ repeat: Enum │ │ createdAt │
│ botId FK │ │ booked: Bool │ │ userId: FK │
└──────────┘ │ botId: FK │ └──────────────┘
└───────────────┘

Table Details​

BotConfig (Multi-Tenant Root Entity)​

model BotConfig {
id String @id @default(cuid())
client String @unique
name String @default("")
password String @default("") // bcrypt hashed
title String
buttonEmoji String
logo String
firstMessage String
fontFamily String
color String // Primary brand color
hoverColor String
messageColor String
fontColor String
system String // AI system prompt
email String // Contact email
allowedHost String // CORS origins
createdAt DateTime @default(now())
confirmed Boolean @default(false)
position String // Widget position
appointmentsEnabled Boolean @default(false)
availableDates Json? // Legacy field
messages Message[]
appointmentDates AppointmentDate[]
passwordResets PasswordReset[]

@@map("bot_configs")
}

Key Features:

  • client: Unique identifier for URL routing (/api/bot/[clientId])
  • password: Always stored as bcrypt hash (12 rounds)
  • allowedHost: Comma-separated list for CORS validation
  • confirmed: Prevents unauthorized bots from being active
  • position: Controls widget placement (bottom-right/left/center)

AppointmentDate (Booking Slots)​

model AppointmentDate {
id String @id @default(cuid())
botConfig BotConfig @relation(fields: [botConfigId], references: [id])
botConfigId String
date String // "YYYY-MM-DD"
time String // "HH:MM"
repeat String // "einmalig" | "wöchentlich" | "täglich"
booked Boolean @default(false)
}

Features:

  • Supports one-time and recurring appointments
  • Duplicate prevention via date_time uniqueness check
  • All duplicates marked as booked simultaneously to prevent race conditions
  • Only booked: false slots returned to widget

PasswordReset (Secure Token Management)​

model PasswordReset {
id String @id @default(cuid())
user BotConfig @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
resetToken String // bcrypt-hashed token
expiresAt DateTime // 1-hour expiry
createdAt DateTime @default(now())

@@map("password_resets")
@@index([userId])
}

Security:

  • Tokens stored as bcrypt hashes (not reversible)
  • 1-hour expiration enforced at validation time
  • Cascade delete when user account removed
  • Old tokens deleted on successful password reset

Database Migrations​

Migration System:

# Development: Create new migration
npx prisma migrate dev --name add_feature

# Production: Apply pending migrations
npx prisma migrate deploy

Automated in Docker:

CMD ["sh", "-c", "npx prisma migrate deploy && npm run start"]

Migration History:

  • 20250721125001_add_position_field: Added widget positioning
  • Migrations tracked in prisma/migrations/migration_lock.toml

API Architecture​

API Route Structure​

/api
├── chat.ts # OpenAI chat completions
├── login.ts # Authentication endpoint
├── bot/
│ ├── [clientId].ts # Get bot configuration
│ ├── appointment.js # Book appointment slot
│ ├── config.js # Create new bot
│ ├── getConfig.js # List all bot configs
│ ├── updateConfig.ts # Update bot settings
│ └── confirm/
│ └── [id].js # Confirm/cancel appointment
└── reset-password/
├── index.ts # Request password reset
├── [token].ts # Validate reset token
└── confirm.ts # Complete password reset

Core API Endpoints​

1. Chat Interface (POST /api/chat)​

Purpose: Process user messages and return AI-generated responses

// Request
{
"id": "clxxx123", // Bot ID
"message": "Hello",
"history": [ // Previous messages
{ "role": "user", "content": "Hi" },
{ "role": "assistant", "content": "Hello!" }
],
"userId": "session-uuid"
}

// Response
{
"reply": "Hello! How can I help you today?"
}

Implementation:

const messages = [
{ role: 'system', content: botConfig.system }, // AI personality
...history, // Context
{ role: 'user', content: message } // Current message
];

const completion = await openai.chat.completions.create({
model: 'gpt-4.1-mini',
messages,
});

Features:

  • ✅ Stores both user and AI messages in database
  • ✅ Maintains conversation context via history array
  • ✅ Custom system prompts per bot configuration
  • ✅ CORS validation with origin whitelisting

2. Bot Configuration (GET /api/bot/[clientId])​

Purpose: Retrieve bot settings and available appointments for widget initialization

// Request
GET /api/bot/clxxx123?t=1638360000000 // Cache-busting query param

// Response
{
"id": "clxxx123",
"title": "Support Bot",
"firstMessage": "How can I help?",
"color": "#4F46E5",
"appointmentsEnabled": true,
"appointmentDates": [
{
"id": "apt-001",
"date": "2025-12-15",
"time": "10:00",
"repeat": "einmalig",
"booked": false
}
]
}

Security Checks:

  1. Validate clientId format and existence
  2. Ensure bot is confirmed (confirmed: true)
  3. Verify request origin against allowedHost
  4. Filter out booked appointments (booked: false)
  5. Set appropriate CORS headers

3. Appointment Booking (POST /api/bot/appointment)​

Purpose: Reserve appointment slot and send confirmation emails

// Request
{
"name": "John Doe",
"email": "john@example.com",
"phone": "+49123456789",
"slot": "2025-12-15 10:00",
"notes": "Need help with product",
"botId": "clxxx123"
}

// Response (Success)
{ "message": "Termin-Anfrage erfolgreich versendet." }

// Response (Conflict - Already Booked)
{ "error": "Dieser Termin wurde bereits gebucht" }

Booking Flow:

// 1. Validate slot exists and is available
const requestedSlot = await prisma.appointmentDate.findFirst({
where: { botConfigId, date, time }
});

// 2. In-memory lock to prevent duplicate submissions
const lockKey = `${requestedSlot.id}:${email}`;
if (sendLocks.has(lockKey)) {
return res.status(409).json({ error: 'Already processing' });
}

// 3. Mark ALL duplicate slots as booked (race condition prevention)
const updated = await prisma.appointmentDate.updateMany({
where: {
botConfigId,
date,
time,
booked: false
},
data: { booked: true }
});

// 4. Send confirmation emails with .ics calendar attachments
await transporter.sendMail({
to: clientEmail,
subject: 'Neue Termin-Anfrage',
html: `<p>Name: ${name}</p><p>Telefon: ${phone}</p>`,
attachments: [{ filename: 'termin.ics', content: icsFile }]
});

Security Features:

  • ✅ Duplicate request prevention via in-memory locks (2-minute auto-expire)
  • ✅ Atomic database updates with updateMany to mark all duplicates
  • ✅ 409 Conflict response when slot already taken
  • ✅ Email validation before processing
  • ✅ CORS validation against bot's allowed origins

4. Bot Creation (POST /api/bot/config)​

Purpose: Create new bot configuration with appointment slots

// Request
{
"client": "demo-shop",
"name": "Shop Bot",
"password": "SecurePass123",
"email": "admin@demo-shop.com",
"allowedHost": "https://demo-shop.com",
"appointmentSlots": [
{
"date": "2025-12-15",
"times": ["10:00", "14:00"],
"repeat": "wöchentlich"
}
]
}

Implementation:

// Hash password with bcrypt (12 rounds)
const hashedPassword = await bcrypt.hash(password, 12);

// Deduplicate appointments using Set
const uniqueAppointments = Array.from(
new Map(
newAppointments.map(apt => [`${apt.date}_${apt.time}`, apt])
).values()
);

// Create bot and appointments in transaction
await prisma.botConfig.create({
data: {
...botData,
password: hashedPassword,
appointmentDates: {
createMany: { data: uniqueAppointments }
}
}
});

5. Password Reset Flow​

5a. Request Reset (POST /api/reset-password)

// Request
{ "email": "user@example.com" }

// Response (Always 200 to prevent user enumeration)
{
"success": true,
"message": "Falls diese E-Mail-Adresse registriert ist, wurde ein Reset-Link gesendet"
}

Backend Logic:

// Rate limiting: Max 3 requests per hour
const recentResets = await prisma.passwordReset.findMany({
where: {
userId: user.id,
createdAt: { gte: oneHourAgo }
}
});

if (recentResets.length >= 3) {
// Still return 200 to prevent timing attacks
return res.status(200).json({ success: true, message: "..." });
}

// Generate secure token and hash it
const rawToken = crypto.randomBytes(32).toString('hex');
const hashed = await bcrypt.hash(rawToken, 12);

// Store hashed token with 1-hour expiry
await prisma.passwordReset.create({
data: {
userId: user.id,
resetToken: hashed,
expiresAt: new Date(Date.now() + 3600000) // 1 hour
}
});

// Send email with plaintext token in URL
await sendEmail({
to: user.email,
subject: 'Passwort zurücksetzen',
html: `<a href="${FRONTEND_URL}/reset-password/${rawToken}">Reset Password</a>`
});

5b. Validate Token (GET /api/reset-password/[token])

// Response (Valid)
{ "success": true }

// Response (Invalid/Expired)
{ "success": false, "message": "Token ungültig oder abgelaufen" }

5c. Complete Reset (POST /api/reset-password/confirm)

// Request
{
"token": "abc123...",
"password": "NewSecurePass123"
}

// Password policy enforcement
if (password.length < 8 || !/[A-Z]/.test(password) || !/\d/.test(password)) {
return res.status(400).json({ error: 'Password policy violation' });
}

// Update password and delete all tokens
const hashedPassword = await bcrypt.hash(password, 12);
await prisma.$transaction([
prisma.botConfig.update({
where: { id: userId },
data: { password: hashedPassword }
}),
prisma.passwordReset.deleteMany({
where: { userId }
})
]);

Widget System​

Architecture​

The widget is a fully self-contained React application bundled as an IIFE (Immediately Invoked Function Expression) that can be embedded into any website via a simple <script> tag.

Key Features:

  • ✅ Shadow DOM Isolation: Prevents CSS/JS conflicts with host page
  • ✅ Zero Dependencies: All styles and logic bundled in single JS file
  • ✅ Responsive Design: Mobile-first with fullscreen mode on mobile devices
  • ✅ Position Flexibility: bottom-right, bottom-left, bottom-center
  • ✅ Theme Customization: Per-bot color schemes and branding

Widget Initialization:

// widget-src/index.tsx
const rootDiv = document.getElementById('widget-root');
const scriptTag = document.currentScript as HTMLScriptElement;
const clientId = scriptTag.getAttribute('data-client-id');
const apiBase = scriptTag.getAttribute('data-api-base');

// Create shadow root for isolation
const shadowRoot = rootDiv.attachShadow({ mode: 'open' });

// Inject styles into shadow DOM
const styleSheet = document.createElement('style');
styleSheet.textContent = styles; // Bundled CSS
shadowRoot.appendChild(styleSheet);

// Mount React app in shadow DOM
const container = document.createElement('div');
shadowRoot.appendChild(container);
ReactDOM.createRoot(container).render(
<ChatWidget clientId={clientId} apiBase={apiBase} />
);

Deployment & Infrastructure​

Docker Architecture​

Multi-Stage Dockerfile

Docker Compose Setup​

services:
db:
image: postgres:17.5
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "127.0.0.1:${DB_PORT}:5432" # Only localhost access
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5

app:
build: .
restart: on-failure
ports:
- "${APP_PORT}:3000"
environment:
DATABASE_URL: ${DATABASE_URL}
depends_on:
db:
condition: service_healthy # Wait for DB health check

volumes:
postgres-data:

Security Features:

  • ✅ Database only accessible from localhost
  • ✅ Health checks ensure database ready before app starts
  • ✅ Named volumes for data persistence
  • ✅ Restart policies for fault tolerance

Environment Configuration​

Required Environment Variables:

# Database
DATABASE_URL=postgresql://user:pass@db:5432/chatbot
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secure_password
POSTGRES_DB=chatbot
DB_PORT=5432

# Application
APP_PORT=3000
FRONTEND_URL=https://start.chatbot-mit-pascal.de

# OpenAI
OPENAI_API_KEY=sk-...

# Authentication
JWT_SECRET=your_secure_jwt_secret
ADMIN_PASSWORD=admin_password_hash

# Email (SMTP)
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=bot@example.com
EMAIL_PASS=smtp_password
EMAIL_BOT=bot@example.com

Code Structure​

chatbot/
├── src/
│ ├── pages/
│ │ └── api/ # Next.js API routes
│ │ ├── chat.ts # OpenAI integration
│ │ ├── login.ts # Authentication
│ │ ├── bot/ # Bot management
│ │ └── reset-password/ # Password reset flow
│ ├── utils/
│ │ ├── cors.ts # CORS validation utility
│ │ └── mailer.ts # Email sending utility
│ ├── config/ # Per-client bot configurations
│ └── types/ # TypeScript definitions
├── widget-src/
│ ├── chatwidget.tsx # Main widget component
│ ├── chatwidget.css # Widget styles
│ ├── index.tsx # Widget entry point
│ └── vite.config.ts # Build configuration
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Migration history
├── public/
│ └── dist/ # Compiled widget (auto-generated)
├── docker-compose.yml # Container orchestration
├── Dockerfile # Multi-stage build
└── package.json # Dependencies & scripts

Security Hardening Checklist​

Application Security​

  • All passwords hashed with bcrypt (12 rounds)
  • JWT tokens with 1-hour expiration
  • CORS validation on all endpoints
  • Password reset tokens expire after 1 hour
  • Rate limiting on password reset (3 requests/hour)
  • No sensitive data in API responses
  • Input validation on all endpoints
  • SQL injection prevention (Prisma parameterized queries)
  • XSS prevention (React auto-escaping)
  • HTTPS enforced in production
  • Environment variables for all secrets

Infrastructure Security​

  • Database only accessible from localhost
  • No root user in Docker containers
  • Minimal attack surface (multi-stage builds)
  • Regular automated backups
  • Health checks for all services
  • Container restart policies
  • Volume encryption (host-level)

Future Security Improvements​

  • Implement 2FA for admin accounts
  • Add rate limiting middleware for all API routes
  • Set up automated security scanning (Dependabot)
  • Implement audit logging for all data modifications
  • Add webhook signatures for external integrations
  • CSP headers for widget embedding
  • Automated SSL certificate renewal

Conclusion​

This chatbot platform demonstrates production-grade fullstack development with strong DevSecOps practices:

✅ Security-First: Bcrypt hashing, JWT authentication, CORS validation, rate limiting ✅ Scalable Architecture: Multi-tenant design, stateless APIs, containerized deployment ✅ Developer Experience: TypeScript type safety, Prisma ORM, hot reloading, automated migrations ✅ Production-Ready: Docker orchestration, automated backups, health checks, error handling ✅ Modern Stack: React 18, Next.js 13, PostgreSQL 17, OpenAI GPT-4

Key Differentiators:

  • Shadow DOM widget isolation for zero conflict embedding
  • Race condition prevention in appointment booking system
  • Comprehensive password reset flow with token expiration
  • Per-bot CORS whitelisting for multi-tenant security
  • Mobile-first responsive design with fullscreen mode

Last Updated: December 2025