npm.io
2.5.0 • Published yesterday

@trustsig/server

Licence
MIT
Version
2.5.0
Deps
3
Size
129 kB
Vulns
0
Weekly
0

@trustsig/server

Node.js and Edge SDK for TrustSig bot protection. Handles token verification and local decryption. Requires Node.js 18+.

Installation

npm install @trustsig/server

Usage

import { TrustSig } from '@trustsig/server';

const ts = new TrustSig({ secretKey: process.env.TRUSTSIG_SECRET_KEY });

const token = request.headers.get('X-TrustSig-Response'); // string | null

const result = await ts.verifyRemote(token);

if (result.action !== 'ALLOW') {
  throw new Error(`Access denied (${result.error ?? result.action})`);
}

Result contract

verifyLocal and verifyRemote return the same shape and never throw. They always resolve to a decision:

Field Type Notes
action 'ALLOW' | 'CHALLENGE' | 'BLOCK' All failures normalize to BLOCK.
blocked boolean Equals action !== 'ALLOW'.
error TrustSigErrorCode | null null on success; otherwise TOKEN_MISSING, TOKEN_EXPIRED, TOKEN_REUSED, CRYPTO_FAIL, API_FAIL, or MALFORMED_RESPONSE.
is_bot boolean
score number 0-100; higher is more suspicious.
request_id string Correlation id; usable for an app-side replay cache.
factors string[] Contributing signals.
evidence Record<string, unknown> Raw signal detail.
site_key string

Gate access with action !== 'ALLOW' or blocked. Never match the old BLOCK_CRYPTO_FAIL / BLOCK_API_FAIL strings; they no longer exist.

Verification methods

  • verifyLocal(token) decrypts locally with ChaCha20-Poly1305. Zero latency, no network. Checks decryptability, age, and (by default) per-token reuse via an in-process cache. See Replay protection for the cap and how to tune or disable it.
  • verifyRemote(token) validates against the TrustSig Edge API. Replay-resistant via the server-side nonce, full telemetry, configurable timeout. Use it for authentication, payments, and account changes.

Replay protection

verifyLocal keeps a small in-process cache that hashes every accepted token (SHA-256) and caps how many times the same token may be verified. By default the same token is accepted up to 4 times; the 5th call resolves to action: 'BLOCK' with error: 'TOKEN_REUSED'. Entries auto-evict at the token's own expiry (issued_at + maxTokenAgeSeconds, default 300s), so memory growth is bounded by the in-flight token set.

// Defaults: replayProtection on, 4 uses per token.
const ts = new TrustSig({ secretKey });

// Single-use tokens (server-rendered checkout, OTP confirmation).
const oneShot = new TrustSig({ secretKey, maxUsesPerToken: 1 });

// Keep tracking but raise the cap.
const generous = new TrustSig({ secretKey, maxUsesPerToken: 50 });

// Accept the same token unlimited times within its age window.
const noReplay = new TrustSig({ secretKey, replayProtection: false });

// Tests / manual operator reset.
ts.clearReplayCache();

The cache lives in process memory, so it only enforces the cap inside a long-lived server: a Node process, a container, a fly.io machine, a Cloud Run instance with min-instances >= 1, a worker on a queue. It is not preserved across cold starts or shared between horizontally scaled instances. In per-request serverless runtimes, where each invocation spawns a fresh process, the cache always starts empty and the cap cannot be enforced. There, set replayProtection: false to drop the overhead, or call verifyRemote(), which validates the nonce server-side.

Options

Option Type Default Description
secretKey string required Secret key. Server-side only; never expose to the browser.
env PROD | DEMO | DEV PROD Selects the default endpoint host.
endpoint string env-derived Override the verification endpoint.
timeoutMs number 5000 verifyRemote network timeout; abort fails closed.
maxTokenAgeSeconds number 300 Max accepted token age for verifyLocal. Also the eviction TTL for replay-cache entries.
clockSkewSeconds number 30 Tolerance for future-dated tokens.
replayProtection boolean true Track token hashes in process memory and cap reuse via verifyLocal. Set false in serverless runtimes.
maxUsesPerToken number 4 Maximum times the same token may pass verifyLocal before TOKEN_REUSED. Ignored when replayProtection is false.

TrustSig Pro

TrustSigPro is the backend client for TrustSig Pro: account-level fraud intelligence (account takeover, signup abuse, account sharing, bot classification), reputation lists, an identity graph, and a synchronous Events API. It is a separate, opt-in class. The TrustSig class above is unchanged, and the two can be used side by side. Import it from the main entry or from @trustsig/server/pro.

It authenticates with a scoped API credential (pro_sk_...). The project is implicit from the credential, so no project id appears anywhere in the API.

import { TrustSigPro } from '@trustsig/server'; // or '@trustsig/server/pro'

const pro = new TrustSigPro({ apiKey: process.env.TRUSTSIG_PRO_KEY }); // pro_sk_...

// Verify the device token and bind it to your user in one call.
const v = await pro.verify(token, { user_id: user.id, traits: { email: user.email } });
if (v.blocked) return v.decision === 'REVIEW' ? stepUp() : deny();

// Submit a business event for a synchronous fraud verdict.
const e = await pro.submitEvent({
  kind: 'login',
  user_id: user.id,
  request_id: v.request_id, // joins the device verdict from the verify call
  ip: req.ip,
});
if (e.decision === 'BLOCK') return deny();
Decisions

Pro verdicts use ALLOW | REVIEW | BLOCK. REVIEW means step up (MFA, captcha, manual review queue), not deny, and it never overrides BLOCK. Gate on decision !== 'ALLOW' or the blocked boolean:

if (v.decision === 'BLOCK') deny();
else if (v.decision === 'REVIEW') stepUp();
else proceed();
Error handling
Methods On failure
verify, submitEvent Fail-closed. Any transport, auth, quota, or validation failure resolves to decision: 'BLOCK' with error set; never throws. Mirrors the Vanilla TrustSig posture so an allow-list check stays fail-safe.
everything else Throws TrustSigProError with { code, status, retryAfter } so reads and list-writes surface errors normally.

error / code values: TOKEN_MISSING, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, RATE_LIMITED, QUOTA_EXCEEDED, CONFLICT, VALIDATION, API_FAIL, MALFORMED_RESPONSE. On a 429, TrustSigProError.retryAfter carries the server's Retry-After seconds when present.

import { TrustSigProError } from '@trustsig/server';

try {
  const account = await pro.getAccount(userId);
} catch (err) {
  if (err instanceof TrustSigProError && err.code === 'NOT_FOUND') {
    // no such account
  } else if (err instanceof TrustSigProError && err.code === 'RATE_LIMITED') {
    await sleep((err.retryAfter ?? 1) * 1000);
  }
}
Methods

Each method's required credential scope is noted. An empty scope list (or *) grants everything.

Area Methods Scope
Verify verify(token, { user_id?, traits?, claimed_entity_id?, telegram?, idempotencyKey?, wait_for_complete_max_ms? }) verify
Events submitEvent(event, { idempotencyKey? }), listEvents(filters) events / read
Requests getRequest(requestId) read
Accounts getAccount(userId), getAccountSharing(userId), getAccountAlts(userId) read
Entities resolveEntity(stableId), getEntity(id), getEntityLinks(id), getEntityCluster(id), getEntityAlts(id), searchEntities(filters) read
Reputation getReputation(kind, value), setReputationStatus(kind, value, { status, note?, value_raw? }) read / lists
Rules getRules(), replaceRules(rules, description?), activateRules() read / rules
Config getDetectionCatalog(), getActiveConfig() read
Analytics getAnalytics(name, query), getAudit(query) read
Export exportObservations(query), returns a raw NDJSON/CSV string export
Feedback submitLabel(body), listLabels(filters) feedback / read
Webhooks registerWebhook(body), listWebhooks() webhooks / read
Usage / meta getUsage(), getReasonCodes(), getWebhookEvents() any valid credential

verify and submitEvent each count as one monthly check against your plan. Both honor an Idempotency-Key: same body replays the cached response, different body returns CONFLICT. The webhook secret from registerWebhook is returned once; store it to verify inbound deliveries (below).

Verify extras:

  • claimed_entity_id is the entity id your frontend surfaced; the response's entity_match says whether it equals the server-authoritative id (spoof detection).
  • telegram ({ init_data, bot_id }) attests a Telegram Mini App user. The platform validates the signed initData against Telegram's public key (no bot token involved); the verdict's telegram block carries verified, the signed user_id, and user_id_match. Also accepted on submitEvent.
  • Responses include bot_score (inverse of trust_score), evidenced detections, and rule_matches (ids of custom rules that fired).

getRequest(requestId) returns the full analysis record for a request id: bot score, every rule and which matched, evidenced detections, flags, geo/agent/velocity context, and the redacted device fingerprint. Use it to debug a verdict or build an internal review tool.

Rules: replaceRules(rules) replaces the whole rule array and writes a Draft config version; nothing is enforced until activateRules() promotes it. Each rule is { id, name, description?, action, state, expr } where action is block | review | allow | label, state is active | shadow | disabled, and expr chains condition blocks, all/any/not combinators, and raw {signal, operator, value} leaves.

Verifying inbound webhooks

Pro signs each webhook delivery with HMAC-SHA256 over "<t>.<body>" and sends X-TrustSig-Signature: t=<unix>,v1=<hex>. Verify the raw request body before parsing it:

import { verifyWebhookSignature } from '@trustsig/server';
// also available as the static TrustSigPro.verifyWebhookSignature

app.post('/hooks/trustsig', express.raw({ type: '*/*' }), (req, res) => {
  const ok = verifyWebhookSignature(
    endpointSecret,
    req.header('X-TrustSig-Signature'),
    req.body.toString('utf8'),
  );
  if (!ok) return res.status(400).end();
  const event = JSON.parse(req.body.toString('utf8'));
  // handle event
  res.status(200).end();
});

It returns true only for a well-formed, fresh, valid signature. A timestamp outside the tolerance window (default 300s) is rejected to blunt replay. Pass { toleranceSeconds } to tune, { toleranceSeconds: 0 } to disable the freshness check, and { nowMs } in tests.

TrustSigPro options
Option Type Default Description
apiKey string required Scoped API credential (pro_sk_...). Server-side only.
env PROD | DEMO | DEV PROD Selects the default host (PROD is cpro.trustsig.eu, DEV is localhost:9100).
endpoint string env-derived Override the Pro base URL.
timeoutMs number 5000 Per-request network timeout.

Keywords