@touchque/node 1.3.0
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/CHANGELOG.md +84 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/dist/index.d.mts +686 -0
- package/dist/index.d.ts +686 -0
- package/dist/index.js +804 -0
- package/dist/index.mjs +756 -0
- package/package.json +73 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. The
|
|
4
|
+
format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [1.3.0] — 2026-09-06
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- `webauthn.deleteCredential()` return type corrected to `{ deleted: boolean }`
|
|
10
|
+
(the API returns `deleted`, not `success`).
|
|
11
|
+
- Doc: the WebAuthn resource header no longer says "there's no client-side SDK"
|
|
12
|
+
— the browser side is `@touchque/web`.
|
|
13
|
+
|
|
14
|
+
### Note
|
|
15
|
+
- Version bumped to **1.3.0** to align the whole server SDK line (node / go /
|
|
16
|
+
php / python) now that Go/PHP/Python have caught up to full parity — WebAuthn
|
|
17
|
+
resource, `getUser`, and the `telemetryToken` login-response field.
|
|
18
|
+
|
|
19
|
+
## [1.2.0] — 2026-08-27
|
|
20
|
+
|
|
21
|
+
First release published to npm as **`@touchque/node`**.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Reverted** the 1.1.0 rename: the package name is `@touchque/node` again
|
|
25
|
+
(never actually shipped to npm under either name). Every consumer, the
|
|
26
|
+
public docs, and the MCP server already reference `@touchque/node`; the
|
|
27
|
+
cross-language rename in 1.1.0 was applied to the manifests but never to a
|
|
28
|
+
single consumer. Product scoping for other TouchQue products (e.g.
|
|
29
|
+
Compliance) is expressed by the package *name* under the shared
|
|
30
|
+
`@touchque` scope, not by a separate scope.
|
|
31
|
+
- `repository.url` corrected to `https://github.com/Touchque/touchque-sdks`.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- `tq.webauthn.primaryOptions()` / `tq.webauthn.primaryVerify()` — the
|
|
35
|
+
passwordless-primary passkey login flow (no password, no prior
|
|
36
|
+
`login.request`), gated on `TenantPolicy.passwordlessLoginEnabled`.
|
|
37
|
+
Returns `{ requiresStepUp }` when risk/policy demands a second factor.
|
|
38
|
+
- `discoverable?: boolean` on `tq.webauthn.registerOptions()` — registers a
|
|
39
|
+
resident credential with forced user verification and a stable WebAuthn
|
|
40
|
+
user handle (the shape a passwordless-primary login authenticates against).
|
|
41
|
+
- `tq.auth.getUser({ externalUsername })` — fetch a user's link status
|
|
42
|
+
(`{ used, deviceId }`), previously added but unreleased.
|
|
43
|
+
- `telemetryToken` on `LoginRequestResponse` — issued when the integration
|
|
44
|
+
has `behavioralBiometricsEnabled`, for the `@touchque/behavioral-widget`
|
|
45
|
+
browser embed, previously added but unreleased.
|
|
46
|
+
- `WebAuthn` resource test suite (`src/resources/WebAuthn.test.ts`); tests
|
|
47
|
+
for `Auth.getUser()` and the Express middleware.
|
|
48
|
+
|
|
49
|
+
### Packaging
|
|
50
|
+
- `package.json` is now publish-shaped: `files: ["dist"]` (no `src`/config in
|
|
51
|
+
the tarball), a conditional `exports` map (ESM/CJS + per-format types),
|
|
52
|
+
`engines.node >= 18`, `sideEffects: false`, `publishConfig.access public`,
|
|
53
|
+
a `prepublishOnly` build+test guard, and `express` declared as an optional
|
|
54
|
+
peer dependency (it is type-only in the SDK).
|
|
55
|
+
|
|
56
|
+
## [1.1.0] — 2026-08-22
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
- **Breaking:** package renamed from `@touchque/node` to
|
|
60
|
+
`@touchque-authenticator/node` to reflect that this SDK is scoped to the
|
|
61
|
+
TouchQue Authenticator (2FA/MFA) product specifically, not TouchQue as a
|
|
62
|
+
whole (TouchQue's other products, e.g. KVKK Compliance, have their own
|
|
63
|
+
separately-named SDKs).
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- README no longer documents `tq.actionTypes.*` / `tq.users.*` — these
|
|
67
|
+
resources never existed in the SDK; the corresponding unused
|
|
68
|
+
`ActionType`/`ActionTypeOptions` types and the unused `UnlinkSecretOptions`/
|
|
69
|
+
`UnlinkSecretResponse` import in `Auth.ts` were also removed.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
- First real automated test suite (`vitest`, 36 tests covering `HttpClient`
|
|
73
|
+
signing/error-handling, `Auth`, `Login`, `Webhook`, and error classes).
|
|
74
|
+
The previous `package.json` `test` script pointed at a nonexistent
|
|
75
|
+
`test.js` and never ran anything.
|
|
76
|
+
- `LICENSE` (MIT) file, matching the license already declared in
|
|
77
|
+
`package.json`.
|
|
78
|
+
|
|
79
|
+
## [1.0.1] — prior to this changelog
|
|
80
|
+
|
|
81
|
+
Initial public functionality: `Auth` (generateSecret/resetSecret/
|
|
82
|
+
validateSecret), `Login` (request/status/waitForApproval/verify/
|
|
83
|
+
approveWithRecoveryCode), `Webhook` (verify/isValid), Express middleware,
|
|
84
|
+
full TypeScript types.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TouchQue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# @touchque/node
|
|
2
|
+
|
|
3
|
+
The official Node.js SDK for [TouchQue Authenticator](https://touchque.com) — add biometric 2FA to any application in minutes.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@touchque/node)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @touchque/node
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { TouchQue } from '@touchque/node';
|
|
19
|
+
|
|
20
|
+
const tq = new TouchQue({
|
|
21
|
+
apiKey: 'tq_auth_your_key', // From TouchQue Dashboard
|
|
22
|
+
apiSecret: 'your_api_secret', // From TouchQue Dashboard
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Protect any action with biometric 2FA — one line!
|
|
26
|
+
await tq.login.verify({
|
|
27
|
+
externalUsername: 'user@company.com',
|
|
28
|
+
type: 'LOGIN',
|
|
29
|
+
});
|
|
30
|
+
// If we reach here, the user approved on their phone ✅
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- 🔐 **Biometric 2FA** — Face ID, Touch ID, fingerprint verification
|
|
36
|
+
- ⚡ **One-line integration** — `tq.login.verify()` does everything
|
|
37
|
+
- 🛡️ **HMAC-SHA256 signed requests** — every API call is cryptographically authenticated
|
|
38
|
+
- 📱 **Real-time push notifications** — instant approval requests to user devices
|
|
39
|
+
- 🔗 **Express.js middleware** — protect routes with a single middleware function
|
|
40
|
+
- 🪝 **Webhook verification** — validate incoming webhook signatures
|
|
41
|
+
- 📝 **Full TypeScript support** — complete type definitions included
|
|
42
|
+
|
|
43
|
+
## Core Concepts
|
|
44
|
+
|
|
45
|
+
### 1. Generate a Secret (User Onboarding)
|
|
46
|
+
|
|
47
|
+
When a user enables 2FA, generate a secret and show them a QR code:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const { secret, expiresAt } = await tq.auth.generateSecret({
|
|
51
|
+
externalUsername: 'user@company.com',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Display `secret` as a QR code in your UI
|
|
55
|
+
// User scans it with the TouchQue mobile app
|
|
56
|
+
// Secret expires in 60 seconds if not scanned
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Verify an Action (The Core Flow)
|
|
60
|
+
|
|
61
|
+
Protect any sensitive action with biometric verification:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { TouchQueRejectedError, TouchQueTimeoutError } from '@touchque/node';
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await tq.login.verify({
|
|
68
|
+
externalUsername: 'user@company.com',
|
|
69
|
+
type: 'WITHDRAW', // Your custom action type
|
|
70
|
+
referenceId: 'txn_abc123', // Optional: your internal reference
|
|
71
|
+
timeout: 30000, // Optional: 30s timeout (default)
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ✅ User approved — execute the action
|
|
75
|
+
executeWithdrawal();
|
|
76
|
+
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof TouchQueRejectedError) {
|
|
79
|
+
// ❌ User rejected on their device
|
|
80
|
+
} else if (err instanceof TouchQueTimeoutError) {
|
|
81
|
+
// ⏰ User didn't respond in time
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Express.js Middleware
|
|
87
|
+
|
|
88
|
+
Protect any Express route with a single middleware:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { TouchQue, requireTouchQue } from '@touchque/node';
|
|
92
|
+
|
|
93
|
+
const tq = new TouchQue({
|
|
94
|
+
apiKey: 'tq_auth_xxx',
|
|
95
|
+
apiSecret: 'your_secret',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Protect a route — that's it!
|
|
99
|
+
app.post('/api/transfer',
|
|
100
|
+
authenticate, // Your auth middleware
|
|
101
|
+
requireTouchQue(tq, 'BANK_TRANSFER'), // TouchQue 2FA
|
|
102
|
+
(req, res) => {
|
|
103
|
+
// Only reaches here if user approved ✅
|
|
104
|
+
res.json({ success: true });
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Webhook Verification
|
|
110
|
+
|
|
111
|
+
Verify that incoming webhooks are genuinely from TouchQue:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
app.post('/webhooks/touchque',
|
|
115
|
+
express.raw({ type: 'application/json' }),
|
|
116
|
+
(req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const event = tq.webhook.verify({
|
|
119
|
+
rawBody: req.body.toString(),
|
|
120
|
+
signature: req.headers['x-tq-signature'] as string,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
switch (event.event) {
|
|
124
|
+
case 'login.confirmed':
|
|
125
|
+
handleApproval(event);
|
|
126
|
+
break;
|
|
127
|
+
case 'login.rejected':
|
|
128
|
+
handleRejection(event);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
res.sendStatus(200);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// Invalid signature — not from TouchQue!
|
|
135
|
+
res.sendStatus(403);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## API Reference
|
|
142
|
+
|
|
143
|
+
### `new TouchQue(config)`
|
|
144
|
+
|
|
145
|
+
| Parameter | Type | Required | Description |
|
|
146
|
+
|-----------|------|----------|-------------|
|
|
147
|
+
| `apiKey` | `string` | ✅ | Your API key (starts with `tq_auth_`) |
|
|
148
|
+
| `apiSecret` | `string` | ✅ | Your API secret |
|
|
149
|
+
| `baseUrl` | `string` | — | API base URL (default: `https://api-authenticator.touchque.com`) |
|
|
150
|
+
| `timeout` | `number` | — | Request timeout in ms (default: `10000`) |
|
|
151
|
+
|
|
152
|
+
### `tq.auth`
|
|
153
|
+
|
|
154
|
+
| Method | Description |
|
|
155
|
+
|--------|-------------|
|
|
156
|
+
| `.generateSecret({ externalUsername })` | Generate a setup secret for user onboarding |
|
|
157
|
+
| `.resetSecret({ externalUsername })` | Reset (regenerate) a user's secret |
|
|
158
|
+
| `.validateSecret({ secret })` | Validate a setup secret code |
|
|
159
|
+
| `.getUser({ externalUsername })` | Link status (`used`, `deviceId`, …) without sending a push; throws 404 if unknown |
|
|
160
|
+
|
|
161
|
+
### `tq.login`
|
|
162
|
+
|
|
163
|
+
| Method | Description |
|
|
164
|
+
|--------|-------------|
|
|
165
|
+
| `.request({ externalUsername, type, referenceId? })` | Send a 2FA request to the user's device |
|
|
166
|
+
| `.status(requestId)` | Check the status of a login request |
|
|
167
|
+
| `.waitForApproval({ requestId, timeout?, pollInterval? })` | Poll until approved/rejected/expired |
|
|
168
|
+
| `.verify({ externalUsername, type, ...options })` | **Recommended**: Request + wait in one call |
|
|
169
|
+
| `.approveWithRecoveryCode({ requestId, code })` | Approve a pending request using a backup recovery code |
|
|
170
|
+
|
|
171
|
+
The `request()` / `verify()` response carries `telemetryToken` when the
|
|
172
|
+
integration has behavioral biometrics enabled — pass it plus `requestId` to your
|
|
173
|
+
frontend for the [`@touchque/web`](https://www.npmjs.com/package/@touchque/web)
|
|
174
|
+
behavioral widget (`tq.behavioral.attach(...)`). There is no widget in this
|
|
175
|
+
server SDK.
|
|
176
|
+
|
|
177
|
+
### `tq.webauthn` — passkey / FIDO2
|
|
178
|
+
|
|
179
|
+
Phishing-resistant login, alongside the push flow. Server-to-server: the passkey
|
|
180
|
+
ceremony runs in the browser (`@touchque/web`, or `navigator.credentials.*`);
|
|
181
|
+
your backend relays the JSON through these.
|
|
182
|
+
|
|
183
|
+
| Method | Description |
|
|
184
|
+
|--------|-------------|
|
|
185
|
+
| `.registerOptions({ externalUsername, discoverable? })` / `.registerVerify({ externalUsername, response, label? })` | Register a passkey |
|
|
186
|
+
| `.authenticateOptions({ requestId })` / `.authenticateVerify({ requestId, response })` | Approve a pending `login.request` with a passkey |
|
|
187
|
+
| `.primaryOptions({ externalUsername })` / `.primaryVerify({ attemptId, response })` | Passwordless-primary login |
|
|
188
|
+
| `.listCredentials({ externalUsername })` / `.deleteCredential(id)` | Credential management |
|
|
189
|
+
|
|
190
|
+
### `tq.webhook`
|
|
191
|
+
|
|
192
|
+
| Method | Description |
|
|
193
|
+
|--------|-------------|
|
|
194
|
+
| `.verify({ rawBody, signature?, toleranceSeconds? })` | Verify webhook signature (throws on invalid) |
|
|
195
|
+
| `.isValid({ rawBody, signature? })` | Check webhook signature (returns boolean) |
|
|
196
|
+
|
|
197
|
+
### Express Middleware
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
requireTouchQue(tqClient, actionType, options?)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
| Option | Type | Description |
|
|
204
|
+
|--------|------|-------------|
|
|
205
|
+
| `timeout` | `number` | Approval timeout in ms (default: `30000`) |
|
|
206
|
+
| `getUserId` | `(req) => string` | Custom function to extract user ID from request |
|
|
207
|
+
| `getReferenceId` | `(req) => string` | Custom function to extract reference ID |
|
|
208
|
+
|
|
209
|
+
## Error Handling
|
|
210
|
+
|
|
211
|
+
All SDK errors extend `TouchQueError`:
|
|
212
|
+
|
|
213
|
+
| Error Class | When Thrown |
|
|
214
|
+
|-------------|-----------|
|
|
215
|
+
| `TouchQueAPIError` | API returned a non-2xx response |
|
|
216
|
+
| `TouchQueRejectedError` | User rejected the 2FA request |
|
|
217
|
+
| `TouchQueTimeoutError` | User didn't respond in time |
|
|
218
|
+
| `TouchQueWebhookSignatureError` | Webhook signature verification failed |
|
|
219
|
+
| `TouchQueConfigError` | Invalid SDK configuration |
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import {
|
|
223
|
+
TouchQueAPIError,
|
|
224
|
+
TouchQueRejectedError,
|
|
225
|
+
TouchQueTimeoutError,
|
|
226
|
+
} from '@touchque/node';
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
await tq.login.verify({ ... });
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (err instanceof TouchQueRejectedError) {
|
|
232
|
+
console.log('Request ID:', err.requestId);
|
|
233
|
+
}
|
|
234
|
+
if (err instanceof TouchQueAPIError) {
|
|
235
|
+
console.log('HTTP Status:', err.status);
|
|
236
|
+
console.log('Error Data:', err.data);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Security
|
|
242
|
+
|
|
243
|
+
- All requests are signed with **HMAC-SHA256** using your API secret
|
|
244
|
+
- Signatures include method, path, timestamp, nonce, and body hash
|
|
245
|
+
- Webhook verification uses **timing-safe comparison** to prevent timing attacks
|
|
246
|
+
- Your API secret never leaves your server
|
|
247
|
+
|
|
248
|
+
## Requirements
|
|
249
|
+
|
|
250
|
+
- Node.js 18+
|
|
251
|
+
- An active [TouchQue Dashboard](https://authenticator.touchque.com) account
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT © [TouchQue](https://touchque.com)
|