white-label-service
v3.0.0
Published
Secure session-based JSON service for explicitly allowed database resources.
Downloads
17
Readme
white-label-service
white-label-service is a secure, session-based JSON API for explicitly approved MySQL tables and columns. It provides login, logout, CSRF protection, cursor pagination, and basic create/read/update/delete operations without exposing unrestricted database access.
The service is a foundation for a trusted first-party web application. It is not a public anonymous API: every data route requires an authenticated session.
Requirements
- Node.js 20 or newer
- npm 10 or newer
- MySQL
- A
usertable withid,email, and a bcrypt password hash inpassword - A durable
express-session-compatible store in production
Install
npm ciCopy .env.example into your environment manager and replace every placeholder. Do not commit secrets or a populated environment file.
SESSION_SECRET=a-random-secret-containing-at-least-32-characters
RESOURCE_FIELDS={"user":["id","email"],"article":["id","title","body","status"]}
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=white_label_service
DB_PASSWORD=replace-me
DB_NAME=white-label-model
HOST=127.0.0.1
PORT=3000Configuration
| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| SESSION_SECRET | Yes | None | Signs session cookies; must contain at least 32 characters. |
| RESOURCE_FIELDS | Yes | None | JSON object mapping allowed table names to allowed columns. Every resource must include id. |
| DB_USER | Yes | None | MySQL user. |
| DB_NAME | Yes | None | MySQL database. |
| DB_HOST | No | 127.0.0.1 | MySQL host. |
| DB_PORT | No | 3306 | MySQL port. |
| DB_PASSWORD | No | Empty | MySQL password. |
| HOST | No | 127.0.0.1 | Address on which the HTTP server listens. |
| PORT | No | 3000 | HTTP or HTTPS port. |
| BODY_LIMIT | No | 100kb | Maximum JSON request-body size. |
| SESSION_MAX_AGE | No | 7200000 | Rolling session lifetime in milliseconds. |
| TLS_KEY_PATH | No | None | Absolute TLS private-key path; must be paired with TLS_CERT_PATH. |
| TLS_CERT_PATH | No | None | Absolute TLS certificate path; must be paired with TLS_KEY_PATH. |
In production, cookies use the __Host-white-label-service name, Secure, HttpOnly, and SameSite=Strict. The application trusts one reverse-proxy hop in production.
Database indexes
Back up the database before applying schema changes. The included migration adds an index to an allowed resource's id column only when that column has no index, and adds a unique index to user.email only when no unique index exists.
Resolve duplicate email addresses before adding the unique constraint.
npm run migrateRoll back the most recent migration with:
npm run migrate:downRollback removes only indexes created under the migration's own names; it does not remove existing primary keys or indexes.
Start the service
For local development:
npm startThe default URL is http://127.0.0.1:3000. Check liveness without creating a session:
curl http://127.0.0.1:3000/health{"status":true}Supplying both TLS paths makes the application create an HTTPS server. Production systems may instead terminate TLS at a trusted reverse proxy.
Authentication and CSRF flow
A client must keep the session cookie and include the current CSRF token on every non-read request. Authentication regenerates the session, so request a new CSRF token after a successful login.
This shell example performs the complete sequence:
# 1. Start a session and obtain the pre-authentication token.
curl --cookie-jar cookies.txt \
http://127.0.0.1:3000/csrf-token
# 2. Send that token in the login request while preserving the cookie.
curl --cookie cookies.txt --cookie-jar cookies.txt \
--header 'Content-Type: application/json' \
--header 'X-CSRF-Token: PRE_AUTH_TOKEN_FROM_STEP_1' \
--data '{"email":"[email protected]","password":"correct horse battery staple"}' \
http://127.0.0.1:3000/authentication
# 3. Login created a new session. Obtain its token for later writes.
curl --cookie cookies.txt --cookie-jar cookies.txt \
http://127.0.0.1:3000/csrf-tokenA successful login returns:
{
"status": true,
"data": {
"id": 42,
"email": "[email protected]"
}
}Only the user's ID and email are stored in the session. Invalid credentials return HTTP 401. Login attempts are limited to 10 per 15-minute window.
To sign out, send the current token:
curl --cookie cookies.txt \
--header 'X-CSRF-Token: POST_AUTH_TOKEN_FROM_STEP_3' \
--request POST \
http://127.0.0.1:3000/sign-outResource allowlist
RESOURCE_FIELDS is both a table allowlist and a column allowlist:
RESOURCE_FIELDS={"article":["id","title","body","status"],"user":["id","email"]}With this configuration, clients can access article and the public fields of user. They cannot select another table, read password hashes, or submit an unlisted column. Unknown resources return HTTP 404 and unapproved input fields return HTTP 400.
Data endpoints
All endpoints below require an authenticated session. POST, PUT, PATCH, and DELETE also require the current X-CSRF-Token.
| Method | Path | Behavior |
| --- | --- | --- |
| POST | /:table | Inserts an allowlisted object. |
| GET | /:table | Returns one cursor-paginated page. |
| GET | /:table/:id | Returns one record by ID. |
| PUT | /:table/:id | Updates approved fields on one record. |
| PATCH | /:table/:id | Updates approved fields on one record. |
| DELETE | /:table/:id | Deletes one record. |
Single-resource reads, updates, and deletes return HTTP 404 with Resource not found when the selected ID does not exist.
Example read:
curl --cookie cookies.txt \
'http://127.0.0.1:3000/article?limit=25&status=published'{
"status": true,
"data": {
"items": [
{"id": 1, "title": "First article", "body": "..."}
],
"nextCursor": "eyJpZCI6MjV9"
}
}Pass nextCursor back as after to retrieve the next page:
curl --cookie cookies.txt \
'http://127.0.0.1:3000/article?limit=25&after=eyJpZCI6MjV9'The default limit is 100; valid limits are 1 through 500. Other query parameters are equality filters and must name allowlisted fields.
Example update:
curl --cookie cookies.txt \
--header 'Content-Type: application/json' \
--header 'X-CSRF-Token: POST_AUTH_TOKEN_FROM_STEP_3' \
--request PATCH \
--data '{"title":"Updated title"}' \
http://127.0.0.1:3000/article/1Responses use {"status":true,"data":...} on success and {"status":false,"error":"..."} on failure. Unexpected server errors are reported with a generic message instead of leaking internal details.
Production integration
The built-in memory session store is suitable only for local development. Production startup refuses to proceed without a supplied session store.
Import startServer to provide one:
const {startServer} = require('white-label-service');
startServer({
sessionStore: myDurableExpressSessionStore
});The returned server exposes an asynchronous shutdown() method that stops accepting connections and closes the Knex pool. The command-line server calls it automatically for SIGINT and SIGTERM:
const server = startServer({
sessionStore: myDurableExpressSessionStore
});
await server.shutdown();For tests or embedding in another server, createApp(options) returns the configured Express application without opening a network port. Both functions also accept injected config, database, baseEndpoint, and authenticationEndpoint values where applicable.
Version 2 migration notes
Version 2 intentionally breaks compatibility with the prototype release:
- Passwords must be bcrypt hashes.
- All data routes require a session.
- Writes require CSRF protection.
POST /signOutbecamePOST /sign-out.- Secrets, database credentials, and optional TLS paths come from environment variables.
- Node.js 20 or newer is required.
- The committed development TLS key and expired certificate were removed.
The removed private key remains in Git history. Never reuse that key or certificate.
Development checks
npm run typecheck
npm test
npm run coverage
npm run auditThe npm package includes only the runtime application, database migrations, Knex configuration, environment example, and README. Tests and development-only configuration are not published.
TypeScript development and version 3.0.0 migration
Implementation code now uses strict TypeScript. Builds emit JavaScript, source maps with embedded source, and .d.ts declarations into dist. JavaScript callers can still use the package without compiling TypeScript themselves. JSDoc comments describe parameters, return values, lifecycle behavior, and validation at the implementation, and are retained in declarations.
import {createApp, type AppOptions} from 'white-label-service';
const options: AppOptions = {
sessionStore: myDurableExpressSessionStore
};
const app = createApp(options);AppOptions describes injectable configuration, Knex, session store, and endpoint dependencies. ManagedServer describes the listener returned by startServer, including shutdown(): Promise<void>. Express session types include the public user identity and CSRF token. Type checking complements, and does not replace, runtime request validation.
This is a major release because runtime files have moved to compiled paths. require('white-label-service') and named imports still resolve the main API. Replace direct source imports such as app/app.js with the package entry or dist/app/app.js. Build before starting or migrating:
npm run build
npm start
# With the required environment variables and a reviewed database backup:
npm run migrateThe Knex CLI uses dist/knexfile.js, which resolves compiled .js migrations relative to itself. Migration source remains in migrations/*.ts. Database credentials and resource allowlists are still required. Existing version 2 authentication and CSRF migration requirements continue to apply.
Verification and coverage
npm ci --ignore-scripts
npm run typecheck
npm test
npm run coverage
npm pack --dry-runnpm test builds the code, checks TypeScript consumer examples against the emitted declarations, and runs the tests. npm run coverage additionally enforces 100% statements, branches, functions, and lines for each implementation file. Unexecuted implementation files count toward the result; declaration-only files contain no executable code and are excluded. Reports are written to coverage, including lcov.info for coverage viewers. CI runs the same gate and checks committed build output for drift.
Tests exercise the compiled JavaScript interface used by downstream callers. Coverage is an execution metric, not proof that all possible inputs or external integrations are correct. Service database tests verify query construction and migration decisions without connecting to a production MySQL database.
To undo this migration, revert its commit and run npm ci from the restored lockfile. No npm release, database migration, or production deployment is performed by these development changes.
