@truesift/express 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -241
- package/dist/index.js +94 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
Server-side TypeScript SDK client for the TrueSift human verification API.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@truesift/express` is designed for backend environments such as Express, Node.js services, and server-side route handlers. It must not be imported into browser/client code.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
```txt
|
|
10
|
+
Package: @truesift/express
|
|
11
|
+
Runtime: Node.js 20+
|
|
12
|
+
Module format: ESM
|
|
13
|
+
Language: TypeScript
|
|
14
|
+
License: MIT
|
|
15
|
+
```
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
@@ -18,9 +20,7 @@ This package is designed for Node.js and Express backend environments. It can al
|
|
|
18
20
|
|
|
19
21
|
The SDK provides a small, typed, predictable service client for communicating with the TrueSift API.
|
|
20
22
|
|
|
21
|
-
It
|
|
22
|
-
|
|
23
|
-
The SDK should help backend projects:
|
|
23
|
+
It helps backend projects:
|
|
24
24
|
|
|
25
25
|
- create verification challenges
|
|
26
26
|
- verify submitted challenges
|
|
@@ -29,6 +29,8 @@ The SDK should help backend projects:
|
|
|
29
29
|
- handle transport and API errors consistently
|
|
30
30
|
- keep secret credentials out of frontend code
|
|
31
31
|
|
|
32
|
+
The SDK is intentionally thin. It does not own your application logic.
|
|
33
|
+
|
|
32
34
|
---
|
|
33
35
|
|
|
34
36
|
## What this SDK is not
|
|
@@ -37,7 +39,7 @@ This SDK is not:
|
|
|
37
39
|
|
|
38
40
|
- a frontend SDK
|
|
39
41
|
- a React package
|
|
40
|
-
- a Next.js
|
|
42
|
+
- a Next.js client package
|
|
41
43
|
- a UI package
|
|
42
44
|
- an Express application
|
|
43
45
|
- an Express router
|
|
@@ -47,7 +49,7 @@ This SDK is not:
|
|
|
47
49
|
- a policy engine
|
|
48
50
|
- an admin dashboard client
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
Your backend remains responsible for request handling, validation, storage, user-facing messages, telemetry, fail-open/fail-closed behavior, and business decisions.
|
|
51
53
|
|
|
52
54
|
---
|
|
53
55
|
|
|
@@ -61,41 +63,15 @@ Do not import this package in:
|
|
|
61
63
|
- browser scripts
|
|
62
64
|
- frontend bundles
|
|
63
65
|
- public JavaScript
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
The SDK uses a `secretKey`, which must never leave the backend.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Package name
|
|
71
|
-
|
|
72
|
-
Current package identity:
|
|
73
|
-
|
|
74
|
-
```txt
|
|
75
|
-
@truesift/express
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
The package is currently marked as private and is not published yet.
|
|
79
|
-
|
|
80
|
-
Possible future distribution options:
|
|
81
|
-
|
|
82
|
-
- public npm package
|
|
83
|
-
- private npm package
|
|
84
|
-
- GitHub Packages
|
|
85
|
-
- internal registry
|
|
86
|
-
- own package server
|
|
87
|
-
- local workspace package
|
|
66
|
+
- files using `NEXT_PUBLIC_*` secrets
|
|
67
|
+
- any code that can be shipped to the browser
|
|
88
68
|
|
|
89
|
-
The SDK
|
|
69
|
+
The SDK uses a `secretKey`. That key must never leave the backend.
|
|
90
70
|
|
|
91
71
|
---
|
|
92
72
|
|
|
93
73
|
## Installation
|
|
94
74
|
|
|
95
|
-
The package is not published yet.
|
|
96
|
-
|
|
97
|
-
When published, installation will look like:
|
|
98
|
-
|
|
99
75
|
```bash
|
|
100
76
|
pnpm add @truesift/express
|
|
101
77
|
```
|
|
@@ -106,168 +82,249 @@ or:
|
|
|
106
82
|
npm install @truesift/express
|
|
107
83
|
```
|
|
108
84
|
|
|
109
|
-
For local development, use the package directly from the project directory or through a workspace setup.
|
|
110
|
-
|
|
111
85
|
---
|
|
112
86
|
|
|
113
87
|
## Requirements
|
|
114
88
|
|
|
115
89
|
```txt
|
|
116
90
|
Node.js >= 20.0.0
|
|
117
|
-
TypeScript
|
|
118
91
|
ESM runtime
|
|
119
92
|
Server-side environment
|
|
120
93
|
```
|
|
121
94
|
|
|
122
95
|
The SDK uses the native `fetch` available in modern Node.js versions.
|
|
123
96
|
|
|
124
|
-
No browser runtime is targeted.
|
|
125
|
-
|
|
126
97
|
---
|
|
127
98
|
|
|
128
|
-
##
|
|
99
|
+
## Environment configuration
|
|
129
100
|
|
|
130
|
-
The
|
|
101
|
+
The SDK does **not** automatically read `.env` files.
|
|
131
102
|
|
|
132
|
-
|
|
133
|
-
createBotGuardClient(config)
|
|
134
|
-
BotGuardClient
|
|
135
|
-
client.createChallenge(input)
|
|
136
|
-
client.verifyChallenge(input)
|
|
103
|
+
Your backend application must read and validate environment variables, then pass the resolved values to `createTrueSiftClient()` or `createBotGuardClient()`.
|
|
137
104
|
|
|
138
|
-
|
|
139
|
-
isReview(result)
|
|
140
|
-
isBlocked(result)
|
|
105
|
+
Recommended backend environment variables:
|
|
141
106
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
107
|
+
```env
|
|
108
|
+
TRUESIFT_API_BASE_URL=https://your-truesift-api.example.com/api/v1/botguard
|
|
109
|
+
TRUESIFT_SITE_KEY=site_xxxxxxxxxxxxxxxxxxxx
|
|
110
|
+
TRUESIFT_SECRET_KEY=secret_xxxxxxxxxxxxxxxxxxxx
|
|
111
|
+
TRUESIFT_DEFAULT_ORIGIN=https://www.example.com
|
|
112
|
+
TRUESIFT_DEFAULT_ACTION=website_check
|
|
113
|
+
TRUESIFT_TIMEOUT_MS=2500
|
|
114
|
+
TRUESIFT_FAIL_OPEN=true
|
|
149
115
|
```
|
|
150
116
|
|
|
151
|
-
|
|
117
|
+
`TRUESIFT_FAIL_OPEN` is **not** an SDK option. It is a project-level policy flag. Your backend decides what to do when TrueSift is unavailable.
|
|
152
118
|
|
|
153
119
|
---
|
|
154
120
|
|
|
155
|
-
##
|
|
121
|
+
## Where do the keys come from?
|
|
156
122
|
|
|
157
|
-
|
|
123
|
+
The SDK does not generate `siteKey` or `secretKey`.
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
125
|
+
They must be issued by the TrueSift service owner, admin panel, provisioning script, or trusted internal configuration process.
|
|
126
|
+
|
|
127
|
+
Typical provisioning model:
|
|
128
|
+
|
|
129
|
+
```txt
|
|
130
|
+
1. A site/application is registered in the TrueSift service.
|
|
131
|
+
2. The service issues a siteKey and secretKey for that site.
|
|
132
|
+
3. The backend stores these values in its private environment configuration.
|
|
133
|
+
4. The backend creates the SDK client using these values.
|
|
134
|
+
5. The frontend never receives the secretKey.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Credential meaning:
|
|
138
|
+
|
|
139
|
+
```txt
|
|
140
|
+
TRUESIFT_API_BASE_URL
|
|
141
|
+
Base URL of the TrueSift API service.
|
|
142
|
+
|
|
143
|
+
TRUESIFT_SITE_KEY
|
|
144
|
+
Site/application identifier. It identifies which protected site or app is making the request.
|
|
145
|
+
|
|
146
|
+
TRUESIFT_SECRET_KEY
|
|
147
|
+
Backend-only credential used to verify trusted server-side requests.
|
|
148
|
+
|
|
149
|
+
TRUESIFT_DEFAULT_ORIGIN
|
|
150
|
+
The default public origin of the protected site.
|
|
151
|
+
|
|
152
|
+
TRUESIFT_DEFAULT_ACTION
|
|
153
|
+
A stable action name such as website_check, contact_form, lead_form, ticket_public_submit.
|
|
154
|
+
|
|
155
|
+
TRUESIFT_TIMEOUT_MS
|
|
156
|
+
SDK request timeout in milliseconds. Recommended default: 2500.
|
|
157
|
+
|
|
158
|
+
TRUESIFT_FAIL_OPEN
|
|
159
|
+
Optional application policy. If true, your app may continue the business flow when TrueSift is unavailable.
|
|
167
160
|
```
|
|
168
161
|
|
|
169
|
-
|
|
162
|
+
Never create or hard-code fake production secrets in source code.
|
|
170
163
|
|
|
171
|
-
|
|
164
|
+
---
|
|
172
165
|
|
|
173
|
-
|
|
166
|
+
## Minimal backend configuration example
|
|
174
167
|
|
|
175
168
|
```ts
|
|
176
|
-
import {
|
|
169
|
+
import { createTrueSiftClient } from '@truesift/express';
|
|
177
170
|
|
|
178
|
-
const trueSiftClient =
|
|
171
|
+
const trueSiftClient = createTrueSiftClient({
|
|
179
172
|
apiBaseUrl: process.env.TRUESIFT_API_BASE_URL,
|
|
180
173
|
siteKey: process.env.TRUESIFT_SITE_KEY,
|
|
181
174
|
secretKey: process.env.TRUESIFT_SECRET_KEY,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
timeoutMs: 2500
|
|
175
|
+
defaultOrigin: process.env.TRUESIFT_DEFAULT_ORIGIN,
|
|
176
|
+
defaultAction: process.env.TRUESIFT_DEFAULT_ACTION,
|
|
177
|
+
timeoutMs: Number(process.env.TRUESIFT_TIMEOUT_MS ?? 2500),
|
|
185
178
|
});
|
|
186
179
|
```
|
|
187
180
|
|
|
181
|
+
For production projects, validate these values with your existing config layer before creating the client.
|
|
182
|
+
|
|
188
183
|
---
|
|
189
184
|
|
|
190
|
-
##
|
|
185
|
+
## Optional configuration pattern
|
|
191
186
|
|
|
192
|
-
|
|
187
|
+
For observe-mode or staged integrations, you may keep TrueSift optional in the host backend.
|
|
193
188
|
|
|
194
|
-
|
|
189
|
+
Example policy:
|
|
195
190
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
|
|
191
|
+
```txt
|
|
192
|
+
if TrueSift config is missing:
|
|
193
|
+
do not create the SDK client
|
|
194
|
+
continue the main business flow
|
|
195
|
+
log a short non-secret warning in development only
|
|
196
|
+
|
|
197
|
+
if TrueSift request fails:
|
|
198
|
+
use project policy:
|
|
199
|
+
fail-open -> continue flow
|
|
200
|
+
fail-closed -> reject request
|
|
201
|
+
```
|
|
203
202
|
|
|
204
|
-
|
|
203
|
+
This policy belongs to your backend application, not the SDK.
|
|
205
204
|
|
|
206
205
|
---
|
|
207
206
|
|
|
208
|
-
##
|
|
207
|
+
## Public API
|
|
209
208
|
|
|
210
|
-
|
|
209
|
+
Main client API:
|
|
211
210
|
|
|
212
|
-
|
|
211
|
+
```ts
|
|
212
|
+
createTrueSiftClient(config)
|
|
213
|
+
createBotGuardClient(config)
|
|
213
214
|
|
|
214
|
-
|
|
215
|
+
TrueSiftClient
|
|
216
|
+
BotGuardClient
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
client.createChallenge(input)
|
|
219
|
+
client.verifyChallenge(input)
|
|
220
|
+
```
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
Decision helpers:
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
```ts
|
|
225
|
+
isAllowed(result)
|
|
226
|
+
isReview(result)
|
|
227
|
+
isBlocked(result)
|
|
221
228
|
|
|
222
|
-
|
|
229
|
+
isAllowedDecision(decision)
|
|
230
|
+
isReviewDecision(decision)
|
|
231
|
+
isBlockedDecision(decision)
|
|
223
232
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- through another project-specific flow
|
|
233
|
+
createDecisionFlags(decision)
|
|
234
|
+
normalizeDecision(value)
|
|
235
|
+
isTrueSiftDecision(value)
|
|
236
|
+
```
|
|
229
237
|
|
|
230
|
-
|
|
238
|
+
Error classes:
|
|
231
239
|
|
|
232
|
-
|
|
240
|
+
```ts
|
|
241
|
+
TrueSiftError
|
|
242
|
+
TrueSiftConfigError
|
|
243
|
+
TrueSiftRequestError
|
|
244
|
+
TrueSiftTimeoutError
|
|
245
|
+
TrueSiftAuthError
|
|
246
|
+
TrueSiftResponseError
|
|
247
|
+
TrueSiftApiError
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Compatibility aliases with the original technical BotGuard naming are also exported:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
BotGuardError
|
|
254
|
+
BotGuardConfigError
|
|
255
|
+
BotGuardRequestError
|
|
256
|
+
BotGuardTimeoutError
|
|
257
|
+
BotGuardAuthError
|
|
258
|
+
BotGuardResponseError
|
|
259
|
+
BotGuardApiError
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Creating a challenge
|
|
233
265
|
|
|
234
266
|
```ts
|
|
235
267
|
const challenge = await trueSiftClient.createChallenge({
|
|
236
|
-
origin: 'https://www.example.com',
|
|
237
|
-
action: 'website_check',
|
|
238
268
|
path: '/website-check',
|
|
239
269
|
metadata: {
|
|
240
|
-
form: 'website-check'
|
|
241
|
-
}
|
|
270
|
+
form: 'website-check',
|
|
271
|
+
},
|
|
242
272
|
});
|
|
243
273
|
```
|
|
244
274
|
|
|
245
|
-
|
|
275
|
+
If `siteKey`, `origin`, or `action` are not provided in the input, the SDK uses the configured defaults.
|
|
246
276
|
|
|
247
|
-
|
|
277
|
+
Expected successful result shape:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
{
|
|
281
|
+
success: true,
|
|
282
|
+
challengeId: string,
|
|
283
|
+
challengeToken: string,
|
|
284
|
+
expiresAt: string,
|
|
285
|
+
requestId?: string,
|
|
286
|
+
raw?: unknown
|
|
287
|
+
}
|
|
288
|
+
```
|
|
248
289
|
|
|
249
|
-
|
|
290
|
+
---
|
|
250
291
|
|
|
251
|
-
|
|
292
|
+
## Verifying a challenge
|
|
252
293
|
|
|
253
294
|
```ts
|
|
254
295
|
const result = await trueSiftClient.verifyChallenge({
|
|
255
296
|
challengeId: body.challengeId,
|
|
256
297
|
challengeToken: body.challengeToken,
|
|
257
|
-
origin: 'https://www.example.com',
|
|
258
|
-
action: 'website_check',
|
|
259
298
|
path: '/website-check',
|
|
260
299
|
clientSignals: {
|
|
261
300
|
elapsedMs: body.elapsedMs,
|
|
262
|
-
honeypotValue: body.companyWebsite
|
|
301
|
+
honeypotValue: body.companyWebsite,
|
|
263
302
|
},
|
|
264
303
|
metadata: {
|
|
265
|
-
form: 'website-check'
|
|
266
|
-
}
|
|
304
|
+
form: 'website-check',
|
|
305
|
+
},
|
|
267
306
|
});
|
|
268
307
|
```
|
|
269
308
|
|
|
270
|
-
|
|
309
|
+
Expected successful result shape:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
{
|
|
313
|
+
success: true,
|
|
314
|
+
decision: 'allow' | 'review' | 'block',
|
|
315
|
+
score: number,
|
|
316
|
+
reasonCodes: string[],
|
|
317
|
+
isAllowed: boolean,
|
|
318
|
+
isReview: boolean,
|
|
319
|
+
isBlocked: boolean,
|
|
320
|
+
mode?: 'observe' | 'protect',
|
|
321
|
+
calculatedDecision?: 'allow' | 'review' | 'block',
|
|
322
|
+
effectiveDecision?: 'allow' | 'review' | 'block',
|
|
323
|
+
challengeId?: string,
|
|
324
|
+
requestId?: string,
|
|
325
|
+
raw?: unknown
|
|
326
|
+
}
|
|
327
|
+
```
|
|
271
328
|
|
|
272
329
|
---
|
|
273
330
|
|
|
@@ -281,102 +338,95 @@ review
|
|
|
281
338
|
block
|
|
282
339
|
```
|
|
283
340
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
```ts
|
|
287
|
-
isAllowed(result)
|
|
288
|
-
isReview(result)
|
|
289
|
-
isBlocked(result)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
These helpers do not make business decisions.
|
|
341
|
+
A `block` decision is a valid business result, not a transport error.
|
|
293
342
|
|
|
294
|
-
|
|
343
|
+
The SDK does not automatically block users. Your backend decides how to react.
|
|
295
344
|
|
|
296
345
|
---
|
|
297
346
|
|
|
298
|
-
##
|
|
299
|
-
|
|
300
|
-
The SDK does not decide whether a request should continue when TrueSift is unavailable.
|
|
347
|
+
## Observe mode
|
|
301
348
|
|
|
302
|
-
|
|
349
|
+
In observe mode, TrueSift can return decisions without your application blocking users.
|
|
303
350
|
|
|
304
|
-
|
|
351
|
+
This is recommended for early integrations:
|
|
305
352
|
|
|
306
353
|
```txt
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if TrueSift is unavailable, reject the request
|
|
354
|
+
1. call verifyChallenge()
|
|
355
|
+
2. record or inspect the decision
|
|
356
|
+
3. continue the user flow
|
|
357
|
+
4. only switch to protect behavior after enough confidence
|
|
312
358
|
```
|
|
313
359
|
|
|
314
|
-
For early observe-mode integrations, fail-open is usually safer because real users should not be blocked by a temporary verification outage.
|
|
315
|
-
|
|
316
360
|
---
|
|
317
361
|
|
|
318
|
-
##
|
|
362
|
+
## Protect mode
|
|
319
363
|
|
|
320
|
-
In
|
|
364
|
+
In protect mode, your backend may act on `block` decisions.
|
|
321
365
|
|
|
322
|
-
|
|
366
|
+
Example policy:
|
|
323
367
|
|
|
324
|
-
|
|
368
|
+
```txt
|
|
369
|
+
allow -> continue
|
|
370
|
+
review -> continue, flag, or apply extra checks
|
|
371
|
+
block -> reject or slow down according to your business rules
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The SDK only returns normalized data. It does not enforce this policy.
|
|
325
375
|
|
|
326
376
|
---
|
|
327
377
|
|
|
328
|
-
##
|
|
378
|
+
## Fail-open and fail-closed
|
|
329
379
|
|
|
330
|
-
|
|
380
|
+
The SDK does not decide whether a request should continue when TrueSift is unavailable.
|
|
331
381
|
|
|
332
|
-
|
|
382
|
+
Typical backend policies:
|
|
333
383
|
|
|
334
|
-
|
|
384
|
+
```txt
|
|
385
|
+
fail-open:
|
|
386
|
+
if TrueSift is unavailable, continue the business flow
|
|
335
387
|
|
|
336
|
-
|
|
388
|
+
fail-closed:
|
|
389
|
+
if TrueSift is unavailable, reject the request
|
|
390
|
+
```
|
|
337
391
|
|
|
338
|
-
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
- unexpected response shape
|
|
342
|
-
- HTTP 401
|
|
343
|
-
- HTTP 5xx
|
|
344
|
-
- malformed API response
|
|
392
|
+
For public lead forms, website checks, and early observe-mode integrations, fail-open is usually safer because real users should not be blocked by a temporary verification outage.
|
|
393
|
+
|
|
394
|
+
For sensitive endpoints, your backend may choose fail-closed.
|
|
345
395
|
|
|
346
396
|
---
|
|
347
397
|
|
|
348
398
|
## Error model
|
|
349
399
|
|
|
350
|
-
The SDK distinguishes between
|
|
400
|
+
The SDK distinguishes between these error classes:
|
|
351
401
|
|
|
352
402
|
```txt
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
403
|
+
TrueSiftConfigError
|
|
404
|
+
TrueSiftRequestError
|
|
405
|
+
TrueSiftTimeoutError
|
|
406
|
+
TrueSiftAuthError
|
|
407
|
+
TrueSiftResponseError
|
|
408
|
+
TrueSiftApiError
|
|
359
409
|
```
|
|
360
410
|
|
|
361
|
-
|
|
411
|
+
Meaning:
|
|
362
412
|
|
|
363
413
|
```txt
|
|
364
|
-
|
|
414
|
+
TrueSiftConfigError:
|
|
365
415
|
missing or invalid SDK configuration
|
|
366
416
|
|
|
367
|
-
|
|
417
|
+
TrueSiftRequestError:
|
|
368
418
|
network-level request failure
|
|
369
419
|
|
|
370
|
-
|
|
420
|
+
TrueSiftTimeoutError:
|
|
371
421
|
request aborted after timeout
|
|
372
422
|
|
|
373
|
-
|
|
423
|
+
TrueSiftAuthError:
|
|
374
424
|
API returned an authentication or authorization error
|
|
375
425
|
|
|
376
|
-
|
|
426
|
+
TrueSiftResponseError:
|
|
377
427
|
API response was malformed or unexpected
|
|
378
428
|
|
|
379
|
-
|
|
429
|
+
TrueSiftApiError:
|
|
380
430
|
API returned a structured error response
|
|
381
431
|
```
|
|
382
432
|
|
|
@@ -394,23 +444,43 @@ Recommended default:
|
|
|
394
444
|
|
|
395
445
|
The timeout is implemented with `AbortController`.
|
|
396
446
|
|
|
397
|
-
The SDK
|
|
447
|
+
The SDK does not retry requests automatically.
|
|
448
|
+
|
|
449
|
+
Important:
|
|
450
|
+
|
|
451
|
+
```txt
|
|
452
|
+
verifyChallenge() must not be retried automatically
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Challenge tokens may be single-use. Retrying verification can produce incorrect behavior or falsely consume a token.
|
|
398
456
|
|
|
399
457
|
---
|
|
400
458
|
|
|
401
|
-
##
|
|
459
|
+
## Secret key rules
|
|
460
|
+
|
|
461
|
+
The `secretKey` is a backend-only credential.
|
|
462
|
+
|
|
463
|
+
Never:
|
|
402
464
|
|
|
403
|
-
|
|
465
|
+
- send it to the browser
|
|
466
|
+
- expose it through frontend environment variables
|
|
467
|
+
- log it
|
|
468
|
+
- include it in screenshots
|
|
469
|
+
- include it in client payloads
|
|
470
|
+
- store it in public repositories
|
|
471
|
+
- return it from API responses
|
|
404
472
|
|
|
405
|
-
|
|
473
|
+
The SDK includes redaction helpers, but your application should still avoid logging full request bodies, headers, cookies, tokens, and environment dumps.
|
|
406
474
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Site key rules
|
|
478
|
+
|
|
479
|
+
The `siteKey` identifies a protected site/application.
|
|
410
480
|
|
|
411
|
-
|
|
481
|
+
It is less sensitive than `secretKey`, but backend-driven integrations normally do not need to expose it to the frontend.
|
|
412
482
|
|
|
413
|
-
|
|
483
|
+
If a future frontend challenge widget needs a public site key, expose only the site key and never the secret key.
|
|
414
484
|
|
|
415
485
|
---
|
|
416
486
|
|
|
@@ -429,18 +499,18 @@ Conceptual controller flow:
|
|
|
429
499
|
4. Extract client signals
|
|
430
500
|
5. Call verifyChallenge()
|
|
431
501
|
6. If TrueSift transport error and project policy is fail-open:
|
|
432
|
-
continue business flow
|
|
502
|
+
continue business flow
|
|
433
503
|
7. If result is blocked and project is in protect mode:
|
|
434
504
|
return project-specific rejection response
|
|
435
505
|
8. Otherwise:
|
|
436
|
-
continue
|
|
506
|
+
continue normal business flow
|
|
437
507
|
```
|
|
438
508
|
|
|
439
509
|
---
|
|
440
510
|
|
|
441
511
|
## Next.js backend usage concept
|
|
442
512
|
|
|
443
|
-
The
|
|
513
|
+
The SDK may be used in Next.js route handlers, but this package must not import Next.js types.
|
|
444
514
|
|
|
445
515
|
Conceptual flow:
|
|
446
516
|
|
|
@@ -449,7 +519,7 @@ app/api/some-form/route.ts
|
|
|
449
519
|
↓
|
|
450
520
|
server-only environment config
|
|
451
521
|
↓
|
|
452
|
-
|
|
522
|
+
createTrueSiftClient()
|
|
453
523
|
↓
|
|
454
524
|
verifyChallenge()
|
|
455
525
|
↓
|
|
@@ -460,20 +530,27 @@ Do not import this SDK into client components.
|
|
|
460
530
|
|
|
461
531
|
---
|
|
462
532
|
|
|
463
|
-
##
|
|
533
|
+
## Safe logging
|
|
464
534
|
|
|
465
|
-
|
|
535
|
+
Recommended logging behavior:
|
|
466
536
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
537
|
+
```txt
|
|
538
|
+
development:
|
|
539
|
+
short warnings are acceptable
|
|
540
|
+
never log secretKey, challengeToken, cookies, authorization headers, or full request bodies
|
|
541
|
+
|
|
542
|
+
production:
|
|
543
|
+
log only high-level failure category, requestId, endpoint/action, and non-sensitive context
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The SDK exposes redaction helpers:
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
redactTrueSiftClientConfig(config)
|
|
550
|
+
redactBotGuardConfig(config)
|
|
551
|
+
redactSensitiveRecord(record)
|
|
552
|
+
redactSensitiveValue(value)
|
|
553
|
+
```
|
|
477
554
|
|
|
478
555
|
---
|
|
479
556
|
|
|
@@ -491,16 +568,16 @@ Run typecheck:
|
|
|
491
568
|
pnpm typecheck
|
|
492
569
|
```
|
|
493
570
|
|
|
494
|
-
|
|
571
|
+
Run tests:
|
|
495
572
|
|
|
496
573
|
```bash
|
|
497
|
-
pnpm
|
|
574
|
+
pnpm test
|
|
498
575
|
```
|
|
499
576
|
|
|
500
|
-
|
|
577
|
+
Build package:
|
|
501
578
|
|
|
502
579
|
```bash
|
|
503
|
-
pnpm
|
|
580
|
+
pnpm build
|
|
504
581
|
```
|
|
505
582
|
|
|
506
583
|
Run all checks:
|
|
@@ -509,52 +586,28 @@ Run all checks:
|
|
|
509
586
|
pnpm check
|
|
510
587
|
```
|
|
511
588
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
## Current build setup
|
|
515
|
-
|
|
516
|
-
The package currently uses:
|
|
517
|
-
|
|
518
|
-
```txt
|
|
519
|
-
TypeScript 6
|
|
520
|
-
tsup
|
|
521
|
-
Vitest
|
|
522
|
-
Node.js 20 target
|
|
523
|
-
ESM output
|
|
524
|
-
DTS output
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
The generated package output is written to:
|
|
589
|
+
Create package preview:
|
|
528
590
|
|
|
529
|
-
```
|
|
530
|
-
|
|
591
|
+
```bash
|
|
592
|
+
npm pack --dry-run
|
|
531
593
|
```
|
|
532
594
|
|
|
533
595
|
---
|
|
534
596
|
|
|
535
|
-
##
|
|
536
|
-
|
|
537
|
-
This package directory may later become:
|
|
538
|
-
|
|
539
|
-
- a standalone Git repository
|
|
540
|
-
- part of a monorepo
|
|
541
|
-
- a private npm package source
|
|
542
|
-
- a public npm package source
|
|
543
|
-
- an internal TrueSift SDK workspace
|
|
544
|
-
|
|
545
|
-
The package is designed to remain reusable regardless of the final repository and publishing strategy.
|
|
546
|
-
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
## License
|
|
550
|
-
|
|
551
|
-
This package is currently proprietary and unpublished.
|
|
597
|
+
## Security principles
|
|
552
598
|
|
|
553
|
-
|
|
599
|
+
The SDK follows these principles:
|
|
554
600
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
601
|
+
- server-side only
|
|
602
|
+
- no default logging
|
|
603
|
+
- no secret logging
|
|
604
|
+
- no browser bundle usage
|
|
605
|
+
- no business policy decisions
|
|
606
|
+
- no automatic Express middleware in the first version
|
|
607
|
+
- no automatic retries for challenge verification
|
|
608
|
+
- runtime response validation
|
|
609
|
+
- strict TypeScript types
|
|
610
|
+
- minimal runtime surface
|
|
558
611
|
|
|
559
612
|
---
|
|
560
613
|
|
package/dist/index.js
CHANGED
|
@@ -398,6 +398,28 @@ function requireSuccessTrue(record) {
|
|
|
398
398
|
);
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
+
function hasOwnResponseField(record, fieldName) {
|
|
402
|
+
return Object.prototype.hasOwnProperty.call(record, fieldName);
|
|
403
|
+
}
|
|
404
|
+
function getResponsePayloadRecord(record, primaryFieldName) {
|
|
405
|
+
if (hasOwnResponseField(record, primaryFieldName)) {
|
|
406
|
+
return record;
|
|
407
|
+
}
|
|
408
|
+
const data = record["data"];
|
|
409
|
+
if (data === void 0) {
|
|
410
|
+
return record;
|
|
411
|
+
}
|
|
412
|
+
if (!isRecord(data)) {
|
|
413
|
+
throw createResponseError(
|
|
414
|
+
'TrueSift API response field "data" must be an object when payload fields are nested.',
|
|
415
|
+
{
|
|
416
|
+
fieldName: "data",
|
|
417
|
+
expected: "object"
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return data;
|
|
422
|
+
}
|
|
401
423
|
function requireStringField(record, fieldName) {
|
|
402
424
|
const value = record[fieldName];
|
|
403
425
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
@@ -427,6 +449,16 @@ function optionalStringField(record, fieldName) {
|
|
|
427
449
|
}
|
|
428
450
|
return value.trim();
|
|
429
451
|
}
|
|
452
|
+
function optionalStringFieldFromPayloadOrRoot(payloadRecord, rootRecord, fieldName) {
|
|
453
|
+
const payloadValue = optionalStringField(payloadRecord, fieldName);
|
|
454
|
+
if (payloadValue !== void 0) {
|
|
455
|
+
return payloadValue;
|
|
456
|
+
}
|
|
457
|
+
if (payloadRecord === rootRecord) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
return optionalStringField(rootRecord, fieldName);
|
|
461
|
+
}
|
|
430
462
|
function requireFiniteNumberField(record, fieldName) {
|
|
431
463
|
const value = record[fieldName];
|
|
432
464
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -488,6 +520,16 @@ function optionalVerificationModeField(record, fieldName) {
|
|
|
488
520
|
}
|
|
489
521
|
);
|
|
490
522
|
}
|
|
523
|
+
function optionalVerificationModeFieldFromPayloadOrRoot(payloadRecord, rootRecord, fieldName) {
|
|
524
|
+
const payloadValue = optionalVerificationModeField(payloadRecord, fieldName);
|
|
525
|
+
if (payloadValue !== void 0) {
|
|
526
|
+
return payloadValue;
|
|
527
|
+
}
|
|
528
|
+
if (payloadRecord === rootRecord) {
|
|
529
|
+
return void 0;
|
|
530
|
+
}
|
|
531
|
+
return optionalVerificationModeField(rootRecord, fieldName);
|
|
532
|
+
}
|
|
491
533
|
function optionalDecisionField(record, fieldName) {
|
|
492
534
|
const value = record[fieldName];
|
|
493
535
|
if (value === void 0) {
|
|
@@ -495,10 +537,32 @@ function optionalDecisionField(record, fieldName) {
|
|
|
495
537
|
}
|
|
496
538
|
return normalizeDecision(value);
|
|
497
539
|
}
|
|
498
|
-
function
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
540
|
+
function optionalDecisionFieldFromPayloadOrRoot(payloadRecord, rootRecord, fieldName) {
|
|
541
|
+
const payloadValue = optionalDecisionField(payloadRecord, fieldName);
|
|
542
|
+
if (payloadValue !== void 0) {
|
|
543
|
+
return payloadValue;
|
|
544
|
+
}
|
|
545
|
+
if (payloadRecord === rootRecord) {
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
return optionalDecisionField(rootRecord, fieldName);
|
|
549
|
+
}
|
|
550
|
+
function getDecisionMetadata(payloadRecord, rootRecord) {
|
|
551
|
+
const mode = optionalVerificationModeFieldFromPayloadOrRoot(
|
|
552
|
+
payloadRecord,
|
|
553
|
+
rootRecord,
|
|
554
|
+
"mode"
|
|
555
|
+
);
|
|
556
|
+
const calculatedDecision = optionalDecisionFieldFromPayloadOrRoot(
|
|
557
|
+
payloadRecord,
|
|
558
|
+
rootRecord,
|
|
559
|
+
"calculatedDecision"
|
|
560
|
+
);
|
|
561
|
+
const effectiveDecision = optionalDecisionFieldFromPayloadOrRoot(
|
|
562
|
+
payloadRecord,
|
|
563
|
+
rootRecord,
|
|
564
|
+
"effectiveDecision"
|
|
565
|
+
);
|
|
502
566
|
return {
|
|
503
567
|
...mode !== void 0 ? { mode } : {},
|
|
504
568
|
...calculatedDecision !== void 0 ? { calculatedDecision } : {},
|
|
@@ -508,12 +572,17 @@ function getDecisionMetadata(record) {
|
|
|
508
572
|
function validateCreateChallengeResponse(response) {
|
|
509
573
|
const record = requireRecord(response);
|
|
510
574
|
requireSuccessTrue(record);
|
|
511
|
-
const
|
|
575
|
+
const payloadRecord = getResponsePayloadRecord(record, "challengeId");
|
|
576
|
+
const requestId = optionalStringFieldFromPayloadOrRoot(
|
|
577
|
+
payloadRecord,
|
|
578
|
+
record,
|
|
579
|
+
"requestId"
|
|
580
|
+
);
|
|
512
581
|
return {
|
|
513
582
|
success: true,
|
|
514
|
-
challengeId: requireStringField(
|
|
515
|
-
challengeToken: requireStringField(
|
|
516
|
-
expiresAt: requireStringField(
|
|
583
|
+
challengeId: requireStringField(payloadRecord, "challengeId"),
|
|
584
|
+
challengeToken: requireStringField(payloadRecord, "challengeToken"),
|
|
585
|
+
expiresAt: requireStringField(payloadRecord, "expiresAt"),
|
|
517
586
|
...requestId !== void 0 ? { requestId } : {},
|
|
518
587
|
raw: record
|
|
519
588
|
};
|
|
@@ -521,16 +590,27 @@ function validateCreateChallengeResponse(response) {
|
|
|
521
590
|
function validateVerifyChallengeResponse(response) {
|
|
522
591
|
const record = requireRecord(response);
|
|
523
592
|
requireSuccessTrue(record);
|
|
524
|
-
const
|
|
593
|
+
const payloadRecord = getResponsePayloadRecord(record, "decision");
|
|
594
|
+
const decision = normalizeDecision(
|
|
595
|
+
requireStringField(payloadRecord, "decision")
|
|
596
|
+
);
|
|
525
597
|
const decisionFlags = createDecisionFlags(decision);
|
|
526
|
-
const metadata = getDecisionMetadata(record);
|
|
527
|
-
const challengeId =
|
|
528
|
-
|
|
598
|
+
const metadata = getDecisionMetadata(payloadRecord, record);
|
|
599
|
+
const challengeId = optionalStringFieldFromPayloadOrRoot(
|
|
600
|
+
payloadRecord,
|
|
601
|
+
record,
|
|
602
|
+
"challengeId"
|
|
603
|
+
);
|
|
604
|
+
const requestId = optionalStringFieldFromPayloadOrRoot(
|
|
605
|
+
payloadRecord,
|
|
606
|
+
record,
|
|
607
|
+
"requestId"
|
|
608
|
+
);
|
|
529
609
|
return {
|
|
530
610
|
success: true,
|
|
531
611
|
decision,
|
|
532
|
-
score: requireFiniteNumberField(
|
|
533
|
-
reasonCodes: optionalStringArrayField(
|
|
612
|
+
score: requireFiniteNumberField(payloadRecord, "score"),
|
|
613
|
+
reasonCodes: optionalStringArrayField(payloadRecord, "reasonCodes"),
|
|
534
614
|
...challengeId !== void 0 ? { challengeId } : {},
|
|
535
615
|
...requestId !== void 0 ? { requestId } : {},
|
|
536
616
|
...metadata,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truesift/express",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Server-side TypeScript SDK client for TrueSift human verification API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,4 +57,4 @@
|
|
|
57
57
|
"typescript": "^6.0.3",
|
|
58
58
|
"vitest": "^4.1.9"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|