Licensor

Configuration Guide

Comprehensive guide for configuring the WiLicensor Server environment.


Environment Variables

All configuration is managed via environment variables in .env.local.

Database Configuration

POSTGRES_HOST=your-postgres-host.com
POSTGRES_PORT=5432
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_password
POSTGRES_DATABASE=your_database_name
POSTGRES_SSL=true  # Set to 'false' for local development

Notes:


Outage Grace Register

SEEN_KEYS_PATH=/data/seen-keys.json

Optional. During a database outage /api/check grants a short grace period, but
only to keys that have already validated successfully at least once. That list
lives in memory and is mirrored to this file.

The default path is inside the app directory, which is rebuilt on every deploy.
The register therefore survives a process restart but not a redeploy. If the
database fails shortly after a deploy, customers are refused until each has
synced once. That fails closed (a lockout, never an unearned licence) and clears
itself within one sync interval.

Point this at a mounted volume to remove the window. On Coolify, add a persistent
volume and set the variable to a path inside it.


Subscription Expiry Enforcement

ENFORCE_SUBSCRIPTION_EXPIRY=false

Off by default, and it should stay off until you have decided what happens to
customers whose subscription already lapsed.

What it does when on: Stripe subscription events stamp the paid-through date
onto that customer's keys, and each successful renewal pushes it further out. A
key with a past endAt stops validating on its own, so a cancellation that never
reached the server costs at most one billing period instead of granting access
forever. The date is only ever extended, never shortened, so an out-of-order
webhook cannot claw back entitlement.

What it does when off: nothing is written. The server logs the date it would
have stamped, so you can see the effect before committing to it.

Before switching it on: every existing subscription key has no endAt at
all. Turning this on does not retroactively expire them (they get a date only
when their next Stripe event arrives), but the companion reconcile job does:

node scripts/reconcileSubscriptions.js          # report only, changes nothing
node scripts/reconcileSubscriptions.js --json   # same, machine readable

Applying changes needs BOTH --apply and ENFORCE_SUBSCRIPTION_EXPIRY=true.
One without the other reports and exits. Accounts with no Stripe record at all
(comped, manual, legacy) are always left alone for a human to judge.


Session Management

SESSION_SECRET=your_128_character_random_hex_string

Generate secret:

npm run secret

Requirements:

Session Settings:


Email Configuration

WiLicensor uses Gmail SMTP for email delivery.

EMAIL_SENDER=your-gmail@gmail.com
EMAIL_PASSWORD=your_app_specific_password
EMAIL_FROM_ALIAS=License Server
EMAIL_RECIPIENT=admin@yourdomain.com

Gmail Setup:

  1. Enable 2-factor authentication on your Gmail account
  2. Generate App-Specific Password: https://support.google.com/accounts/answer/185833
  3. Use the 16-character app password (not your regular Gmail password)

Email Functions:

Test Email:

node test.js

Stripe Configuration

USE_STRIPE_TEST_KEY=true  # Toggle test/production

# Production keys
STRIPE_SECRET_KEY=sk_live_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx

# Optional: Test keys
STRIPE_SECRET_KEY_TEST=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET_TEST=whsec_test_xxxxx

Setup:

  1. Get keys from Stripe Dashboard
  2. Get webhook secret from Webhooks
  3. Configure webhook endpoint: https://yourdomain.com/webhook/stripe

Supported Events:

Testing Webhooks Locally:

# Install Stripe CLI
stripe listen --forward-to localhost:5000/webhook/stripe

# Trigger test events
stripe trigger customer.subscription.created

Application Settings

SETTINGS=1    # Sync period in days
PORT=5000     # Server port (default: 5000)

SETTINGS:

PORT:


Environment File Management

File Structure

License-Server/
├── .env.local           # Active configuration (gitignored)
├── .env.local.prod      # Production backup
├── .env.local.dev       # Development backup
├── .env example.local   # Template
└── .env copy 2.local    # Alternative template

Creating Configuration

# Copy template
cp ".env copy.local" .env.local

# Generate session secret
npm run secret

# Edit with your credentials
nano .env.local  # or use any text editor

Switching Environments

# Switch to development
mv .env.local .env.local.prod
mv .env.local.dev .env.local

# Switch back to production
mv .env.local .env.local.dev
mv .env.local.prod .env.local

Database Settings

Application Settings Table

The Settings table stores application-wide configuration:

{
  id: INTEGER,
  name: STRING,        // typically 'default'
  displayName: STRING,
  settings: STRING     // JSON stringified
}

Available Settings:

Auto-Add Products

{
  "autoAdd": true
}

When enabled, products are automatically created if they don't exist when referenced by webhooks or API calls.

Configure via Admin Dashboard:

  1. Login to admin dashboard
  2. Navigate to Settings
  3. Toggle "Auto Add Products"
  4. Save

Local Development Configuration

Development Database Setup

Windows (Chocolatey):

choco install postgresql15 --params '/Password:localdev' -y

Create Development Database:

PGPASSWORD=localdev psql -U postgres -c "CREATE DATABASE license_dev;"

Development .env.local:

POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=localdev
POSTGRES_DATABASE=license_dev
POSTGRES_SSL=false

# Email (use test account or production for notifications)
EMAIL_SENDER=your-test-gmail@gmail.com
EMAIL_PASSWORD=test_app_password
EMAIL_FROM_ALIAS=License Server Dev
EMAIL_RECIPIENT=dev@localhost

# Stripe (use test keys)
USE_STRIPE_TEST_KEY=true
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxx

# Session
SESSION_SECRET=generated_secret_from_npm_run_secret

# Application
SETTINGS=1
PORT=5000

Production Configuration Checklist

Before deploying to production, verify:


Configuration Validation

Test Database Connection

# Using environment variables
psql -h $POSTGRES_HOST -U $POSTGRES_USER -d $POSTGRES_DATABASE

# Manual test
psql -h your-host -U your-user -d your-database

Test Email Configuration

node test.js

Expected output:

Email sent: 250 2.0.0 OK ...

Test Stripe Connection

# Install Stripe CLI
stripe listen --forward-to localhost:5000/webhook/stripe

# In another terminal
stripe trigger customer.subscription.created

Security Best Practices

Secrets Management

Never commit:

Use secure storage:

Password Requirements

SESSION_SECRET:

Database Passwords:

Gmail App Passwords:


Troubleshooting Configuration

"Unable to connect to database"

Check:

  1. Database is running: pg_isready -h your-host
  2. Credentials correct in .env.local
  3. SSL setting matches database requirements
  4. Firewall allows connections

"Email sending failed"

Check:

  1. App-specific password used (not regular password)
  2. 2-factor authentication enabled on Gmail
  3. Environment variables set correctly
  4. Test with node test.js

"Invalid session secret"

Check:

  1. SESSION_SECRET is set
  2. Length is 128+ characters
  3. No special characters causing parsing issues
  4. Regenerate with npm run secret


Last Updated: 2025-12-18