Learn by Directing AI
All materials

app.js

jsapp.js
const express = require('express');
const Database = require('better-sqlite3');
const morgan = require('morgan');
const path = require('path');

const app = express();
const PORT = 3000;

app.use(morgan('combined'));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

const db = new Database('/app/data/tourism.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS tourists (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    passport_number TEXT NOT NULL,
    nationality TEXT,
    travel_dates TEXT,
    booking_ref TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

db.exec(`
  CREATE TABLE IF NOT EXISTS bookings (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    tourist_id INTEGER,
    destination TEXT,
    guide_id TEXT,
    status TEXT DEFAULT 'pending',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (tourist_id) REFERENCES tourists(id)
  )
`);

const sampleTourists = [
  ['Kenji Tanaka', 'JP12345678', 'Japanese', '2026-04-15 to 2026-04-22', 'BTC-2026-0142'],
  ['Sarah Mitchell', 'US98765432', 'American', '2026-04-20 to 2026-04-28', 'BTC-2026-0143'],
  ['Hans Weber', 'DE55667788', 'German', '2026-05-01 to 2026-05-10', 'BTC-2026-0144'],
  ['Yuki Sato', 'JP11223344', 'Japanese', '2026-04-18 to 2026-04-25', 'BTC-2026-0145'],
  ['Emma Thompson', 'GB44556677', 'British', '2026-05-05 to 2026-05-12', 'BTC-2026-0146'],
  ['Pierre Dubois', 'FR99887766', 'French', '2026-04-25 to 2026-05-02', 'BTC-2026-0147'],
  ['Li Wei', 'CN33445566', 'Chinese', '2026-05-10 to 2026-05-18', 'BTC-2026-0148'],
  ['Maria Garcia', 'ES77889900', 'Spanish', '2026-05-15 to 2026-05-22', 'BTC-2026-0149'],
];

const existingCount = db.prepare('SELECT COUNT(*) as count FROM tourists').get();
if (existingCount.count === 0) {
  const insert = db.prepare('INSERT INTO tourists (name, passport_number, nationality, travel_dates, booking_ref) VALUES (?, ?, ?, ?, ?)');
  for (const tourist of sampleTourists) {
    insert.run(...tourist);
  }
}

app.get('/', (req, res) => {
  res.render('index', { title: 'Bhutan Tourism Council - Tourism Services Portal' });
});

// VULNERABLE: SQL injection in search endpoint
// The search parameter is concatenated directly into the SQL query
app.get('/search', (req, res) => {
  const query = req.query.q || '';
  try {
    const sql = `SELECT name, nationality, travel_dates, booking_ref FROM tourists WHERE name LIKE '%${query}%' OR booking_ref LIKE '%${query}%'`;
    const results = db.prepare(sql).all();
    res.render('search', { results, query, title: 'Search Results' });
  } catch (err) {
    res.status(500).render('error', { error: err.message, title: 'Error' });
  }
});

// Internal API endpoint -- accessible without authentication from other services
// This API is called by the Guide Management System to look up tourist data
app.get('/api/tourist-data', (req, res) => {
  const tourists = db.prepare('SELECT id, name, passport_number, nationality, travel_dates, booking_ref FROM tourists').all();
  res.json({ tourists });
});

app.get('/api/tourist-data/:id', (req, res) => {
  const tourist = db.prepare('SELECT * FROM tourists WHERE id = ?').get(req.params.id);
  if (tourist) {
    res.json(tourist);
  } else {
    res.status(404).json({ error: 'Tourist not found' });
  }
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Tourism Services Portal running on port ${PORT}`);
});