HIDDEN SPECTRE — server edition 👻

An augmented-reality ghost-hunting web app — a fan-made tribute to the AR play concept of the LEGO® Hidden Side™ app. This version adds a Node/Express backend with a secure admin panel for managing ghost GIFs and QR-based set recognition.

Fan-made. Not affiliated with, sponsored by, or endorsed by the LEGO Group. LEGO® and Hidden Side™ are trademarks of the LEGO Group. This project does not use LEGO logos or artwork.

What's here

  • Game (public/) — WebXR world-tracked AR with a camera+gyro fallback. Ghosts roam and you trap them by holding the reticle on them. Ghosts render as animated GIF billboards when a set is scanned, or as procedural wisps in free-hunt mode.
  • QR set recognition — scan a set's QR code; the game asks the API which ghosts that set unlocks and spawns them (weighted by rarity).
  • Admin panel (/admin) — JWT-protected login to upload/enable/disable/delete ghost GIFs and create/manage sets and their QR codes.
  • Backend (server/) — Express 5, SQLite (better-sqlite3), bcrypt password hashing, JWT auth, image-only uploads via multer.

Architecture

Browser ──► nginx (HTTPS) ──► Node/Express (localhost:3000)
                                  ├─ /                game (static)
                                  ├─ /admin           admin UI (static)
                                  ├─ /api/login       bcrypt + JWT
                                  ├─ /api/ghosts      CRUD + GIF upload   (auth)
                                  ├─ /api/sets        CRUD + ghost links  (auth)
                                  ├─ /api/scan/:code  public — game reads roster
                                  └─ /uploads/*        served GIFs
                                  └─ SQLite: server/data/spectre.db

Security notes

  • Admin writes require a valid JWT (8h expiry). The public game only hits the read-only /api/scan/:code.
  • Passwords are bcrypt-hashed (cost 12). The first admin is created by the seed script from .env.
  • JWT_SECRET must be set or the server refuses to start.
  • Uploads are restricted to GIF/PNG/WebP, max 8 MB, stored with random filenames.
  • .env, the database, and uploaded files are git-ignored.

Deploy on Ubuntu behind nginx

1. Install Node + build tools

better-sqlite3 is a native module and needs a compiler toolchain to build:

sudo apt update
sudo apt install -y nodejs npm build-essential python3
# (or install Node 20+ from NodeSource for a newer runtime)

2. Get the code and install

git clone <this-repo-url> /opt/hidden-spectre
cd /opt/hidden-spectre
npm install         # compiles better-sqlite3 — needs build-essential

3. Configure secrets

cp .env.example .env
# generate a strong secret:
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(48).toString('hex'))" >> .env
# then edit .env to set ADMIN_USER / ADMIN_PASS (used once for seeding)
nano .env

4. Create the first admin

npm run seed        # reads ADMIN_USER / ADMIN_PASS from .env

5. Run it as a service

Create /etc/systemd/system/hidden-spectre.service:

[Unit]
Description=Hidden Spectre server
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/hidden-spectre
ExecStart=/usr/bin/node server/index.js
Restart=on-failure
Environment=NODE_ENV=production
User=www-data

[Install]
WantedBy=multi-user.target
sudo chown -R www-data:www-data /opt/hidden-spectre
sudo systemctl daemon-reload
sudo systemctl enable --now hidden-spectre

6. Reverse-proxy with nginx

In your existing HTTPS server block:

server {
    listen 443 ssl;
    server_name spectre.hideawaygaming.com.au;

    ssl_certificate     /etc/letsencrypt/live/spectre.hideawaygaming.com.au/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/spectre.hideawaygaming.com.au/privkey.pem;

    client_max_body_size 10M;   # allow GIF uploads

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}
sudo nginx -t && sudo systemctl reload nginx

HTTPS is required — the camera, gyro, and WebXR all refuse to run on an insecure origin.

Using it

  1. Go to https://your-host/admin, log in, upload a few ghost GIFs.
  2. Create a set with a code like SET-GRAVEYARD and tick which ghosts it unlocks.
  3. Make a QR code whose payload is exactly that code (any QR generator works) and print/display it.
  4. On a phone, open https://your-host/, tap Scan a Set, scan the QR — those ghosts spawn. Or tap Free hunt for procedural ghosts with no set.

Asset tips for ghost GIFs

  • Transparent background (GIF or, better, animated WebP/PNG for alpha) reads best floating in space.
  • Roughly square or portrait framing; the billboard auto-fits the image aspect.
  • Keep file size modest (a few hundred KB) so they load fast on mobile data.
  • .obj 3D minifigure ghosts are a planned future mode; this version is GIF/image billboards.

Files

server/index.js    Express app + all routes
server/db.js       SQLite schema
server/auth.js     JWT issue/verify
server/upload.js   multer image upload config
server/seed.js     first-admin creation
public/            the game + /admin panel
public/js/qr.js    QR scanning (BarcodeDetector + manual fallback)
S
Description
Node/Express backend + admin panel for Hidden Spectre AR ghost hunt. GIF ghost management, JWT auth, QR-based set recognition. Fan-made tribute, not affiliated with the LEGO Group.
Readme 67 KiB
Languages
JavaScript 61.8%
CSS 26.6%
HTML 11.6%