@dupecom/botcha 0.6.0 โ 0.10.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/README.md +265 -0
- package/dist/lib/client/index.d.ts +81 -3
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +263 -19
- package/dist/lib/client/types.d.ts +47 -0
- package/dist/lib/client/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
**BOTCHA** is a reverse CAPTCHA โ it verifies that visitors are AI agents, not humans. Perfect for AI-only APIs, agent marketplaces, and bot networks.
|
|
13
13
|
|
|
14
14
|
[](https://www.npmjs.com/package/@dupecom/botcha)
|
|
15
|
+
[](https://pypi.org/project/botcha/)
|
|
15
16
|
[](https://opensource.org/licenses/MIT)
|
|
16
17
|
[](./.github/CONTRIBUTING.md)
|
|
17
18
|
|
|
18
19
|
๐ **Website:** [botcha.ai](https://botcha.ai)
|
|
19
20
|
๐ฆ **npm:** [@dupecom/botcha](https://www.npmjs.com/package/@dupecom/botcha)
|
|
21
|
+
๐ **PyPI:** [botcha](https://pypi.org/project/botcha/)
|
|
22
|
+
๐ **Verify:** [@botcha/verify](./packages/verify/) (TS) ยท [botcha-verify](./packages/python-verify/) (Python)
|
|
20
23
|
๐ **OpenAPI:** [botcha.ai/openapi.json](https://botcha.ai/openapi.json)
|
|
21
24
|
|
|
22
25
|
## Why?
|
|
@@ -28,15 +31,29 @@ Use cases:
|
|
|
28
31
|
- ๐ AI-to-AI marketplaces
|
|
29
32
|
- ๐ซ Bot verification systems
|
|
30
33
|
- ๐ Autonomous agent authentication
|
|
34
|
+
- ๐ข Multi-tenant app isolation with email-tied accounts
|
|
35
|
+
- ๐ Per-app metrics dashboard at [botcha.ai/dashboard](https://botcha.ai/dashboard)
|
|
36
|
+
- ๐ง Email verification, account recovery, and secret rotation
|
|
37
|
+
- ๐ค Agent-first dashboard auth (challenge-based login + device code handoff)
|
|
31
38
|
|
|
32
39
|
## Install
|
|
33
40
|
|
|
41
|
+
### TypeScript/JavaScript
|
|
42
|
+
|
|
34
43
|
```bash
|
|
35
44
|
npm install @dupecom/botcha
|
|
36
45
|
```
|
|
37
46
|
|
|
47
|
+
### Python
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install botcha
|
|
51
|
+
```
|
|
52
|
+
|
|
38
53
|
## Quick Start
|
|
39
54
|
|
|
55
|
+
### TypeScript/JavaScript
|
|
56
|
+
|
|
40
57
|
```typescript
|
|
41
58
|
import express from 'express';
|
|
42
59
|
import { botcha } from '@dupecom/botcha';
|
|
@@ -51,6 +68,21 @@ app.get('/agent-only', botcha.verify(), (req, res) => {
|
|
|
51
68
|
app.listen(3000);
|
|
52
69
|
```
|
|
53
70
|
|
|
71
|
+
### Python
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from botcha import BotchaClient, solve_botcha
|
|
75
|
+
|
|
76
|
+
# Client SDK for AI agents
|
|
77
|
+
async with BotchaClient() as client:
|
|
78
|
+
# Get verification token
|
|
79
|
+
token = await client.get_token()
|
|
80
|
+
|
|
81
|
+
# Or auto-solve and fetch protected endpoints
|
|
82
|
+
response = await client.fetch("https://api.example.com/agent-only")
|
|
83
|
+
data = await response.json()
|
|
84
|
+
```
|
|
85
|
+
|
|
54
86
|
## How It Works
|
|
55
87
|
|
|
56
88
|
BOTCHA offers multiple challenge types. The default is **hybrid** โ combining speed AND reasoning:
|
|
@@ -80,6 +112,179 @@ GET /v1/challenges?type=hybrid
|
|
|
80
112
|
GET /v1/reasoning
|
|
81
113
|
```
|
|
82
114
|
|
|
115
|
+
## ๐ JWT Security (Production-Grade)
|
|
116
|
+
|
|
117
|
+
BOTCHA uses **OAuth2-style token rotation** with short-lived access tokens and long-lived refresh tokens.
|
|
118
|
+
|
|
119
|
+
> **๐ Full guide:** [doc/JWT-SECURITY.md](./doc/JWT-SECURITY.md) โ endpoint reference, request/response examples, audience scoping, IP binding, revocation, design decisions.
|
|
120
|
+
|
|
121
|
+
### Token Flow
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
1. Solve challenge โ receive access_token (5min) + refresh_token (1hr)
|
|
125
|
+
2. Use access_token for API calls
|
|
126
|
+
3. When access_token expires โ POST /v1/token/refresh with refresh_token
|
|
127
|
+
4. When compromised โ POST /v1/token/revoke to invalidate immediately
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Security Features
|
|
131
|
+
|
|
132
|
+
| Feature | What it does |
|
|
133
|
+
|---------|-------------|
|
|
134
|
+
| **5-minute access tokens** | Compromise window reduced from 1hr to 5min |
|
|
135
|
+
| **Refresh tokens (1hr)** | Renew access without re-solving challenges |
|
|
136
|
+
| **Audience (`aud`) scoping** | Token for `api.stripe.com` is rejected by `api.github.com` |
|
|
137
|
+
| **Client IP binding** | Optional โ solve on machine A, can't use on machine B |
|
|
138
|
+
| **Token revocation** | `POST /v1/token/revoke` โ KV-backed, fail-open |
|
|
139
|
+
| **JTI (JWT ID)** | Unique ID per token for revocation and audit |
|
|
140
|
+
|
|
141
|
+
### Quick Example
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const client = new BotchaClient({
|
|
145
|
+
audience: 'https://api.example.com', // Scope token to this service
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Auto-handles: challenge โ token โ refresh โ retry on 401
|
|
149
|
+
const response = await client.fetch('https://api.example.com/agent-only');
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
async with BotchaClient(audience="https://api.example.com") as client:
|
|
154
|
+
response = await client.fetch("https://api.example.com/agent-only")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Token Endpoints
|
|
158
|
+
|
|
159
|
+
| Endpoint | Description |
|
|
160
|
+
|----------|-------------|
|
|
161
|
+
| `GET /v1/token` | Get challenge for token flow |
|
|
162
|
+
| `POST /v1/token/verify` | Submit solution โ receive `access_token` + `refresh_token` |
|
|
163
|
+
| `POST /v1/token/refresh` | Exchange `refresh_token` for new `access_token` |
|
|
164
|
+
| `POST /v1/token/revoke` | Invalidate any token immediately |
|
|
165
|
+
|
|
166
|
+
See **[JWT Security Guide](./doc/JWT-SECURITY.md)** for full request/response examples, `curl` commands, and server-side verification.
|
|
167
|
+
|
|
168
|
+
## ๐ข Multi-Tenant API Keys
|
|
169
|
+
|
|
170
|
+
BOTCHA supports **multi-tenant isolation** โ create separate apps with unique API keys for different services or environments.
|
|
171
|
+
|
|
172
|
+
### Why Multi-Tenant?
|
|
173
|
+
|
|
174
|
+
- **Isolation**: Each app gets its own rate limits and analytics
|
|
175
|
+
- **Security**: Tokens are scoped to specific apps via `app_id` claim
|
|
176
|
+
- **Flexibility**: Different services can use the same BOTCHA instance
|
|
177
|
+
- **Tracking**: Per-app usage analytics (coming soon)
|
|
178
|
+
|
|
179
|
+
### Creating an App
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Create a new app (email required)
|
|
183
|
+
curl -X POST https://botcha.ai/v1/apps \
|
|
184
|
+
-H "Content-Type: application/json" \
|
|
185
|
+
-d '{"email": "agent@example.com"}'
|
|
186
|
+
|
|
187
|
+
# Returns (save the secret - it's only shown once!):
|
|
188
|
+
{
|
|
189
|
+
"app_id": "app_abc123",
|
|
190
|
+
"app_secret": "sk_xyz789",
|
|
191
|
+
"email": "agent@example.com",
|
|
192
|
+
"email_verified": false,
|
|
193
|
+
"verification_required": true,
|
|
194
|
+
"warning": "Save your app_secret now โ it cannot be retrieved again! Check your email for a verification code."
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Verify your email with the 6-digit code:
|
|
198
|
+
curl -X POST https://botcha.ai/v1/apps/app_abc123/verify-email \
|
|
199
|
+
-H "Content-Type: application/json" \
|
|
200
|
+
-d '{"code": "123456"}'
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Using Your App ID
|
|
204
|
+
|
|
205
|
+
All challenge and token endpoints accept an optional `app_id` query parameter:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Get challenge with app_id
|
|
209
|
+
curl "https://botcha.ai/v1/challenges?app_id=app_abc123"
|
|
210
|
+
|
|
211
|
+
# Get token with app_id
|
|
212
|
+
curl "https://botcha.ai/v1/token?app_id=app_abc123"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
When you solve a challenge with an `app_id`, the resulting token includes the `app_id` claim.
|
|
216
|
+
|
|
217
|
+
### SDK Support
|
|
218
|
+
|
|
219
|
+
**TypeScript:**
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { BotchaClient } from '@dupecom/botcha/client';
|
|
223
|
+
|
|
224
|
+
const client = new BotchaClient({
|
|
225
|
+
appId: 'app_abc123', // All requests will include this app_id
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const response = await client.fetch('https://api.example.com/agent-only');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Python:**
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from botcha import BotchaClient
|
|
235
|
+
|
|
236
|
+
async with BotchaClient(app_id="app_abc123") as client:
|
|
237
|
+
response = await client.fetch("https://api.example.com/agent-only")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Rate Limiting
|
|
241
|
+
|
|
242
|
+
Each app gets its own rate limit bucket:
|
|
243
|
+
- Default rate limit: 100 requests/hour per app
|
|
244
|
+
- Rate limit key: `rate:app:{app_id}`
|
|
245
|
+
- Fail-open design: KV errors don't block requests
|
|
246
|
+
|
|
247
|
+
### Get App Info
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Get app details (secret is NOT included)
|
|
251
|
+
curl https://botcha.ai/v1/apps/app_abc123
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## ๐ Per-App Metrics Dashboard
|
|
255
|
+
|
|
256
|
+
BOTCHA includes a built-in **metrics dashboard** at [`/dashboard`](https://botcha.ai/dashboard) showing per-app analytics with a terminal-inspired aesthetic.
|
|
257
|
+
|
|
258
|
+
### What You Get
|
|
259
|
+
|
|
260
|
+
- **Overview stats**: Challenges generated, verifications, success rate, avg solve time
|
|
261
|
+
- **Request volume**: Time-bucketed event charts
|
|
262
|
+
- **Challenge types**: Breakdown by speed/hybrid/reasoning/standard
|
|
263
|
+
- **Performance**: p50/p95 solve times, response latency
|
|
264
|
+
- **Errors & rate limits**: Failure tracking
|
|
265
|
+
- **Geographic distribution**: Top countries by request volume
|
|
266
|
+
|
|
267
|
+
### Access
|
|
268
|
+
|
|
269
|
+
Three ways to access โ all require an AI agent:
|
|
270
|
+
|
|
271
|
+
1. **Agent Direct**: Your agent solves a speed challenge via `POST /v1/auth/dashboard` โ gets a session token
|
|
272
|
+
2. **Device Code**: Agent solves challenge via `POST /v1/auth/device-code` โ gets a `BOTCHA-XXXX` code โ human enters it at `/dashboard/code`
|
|
273
|
+
3. **Legacy**: Login with `app_id` + `app_secret` at [botcha.ai/dashboard/login](https://botcha.ai/dashboard/login)
|
|
274
|
+
|
|
275
|
+
Session uses cookie-based auth (HttpOnly, Secure, SameSite=Lax, 1hr expiry).
|
|
276
|
+
|
|
277
|
+
### Email & Recovery
|
|
278
|
+
|
|
279
|
+
- Email is **required** at app creation (`POST /v1/apps` with `{"email": "..."}`)
|
|
280
|
+
- Verify email with a 6-digit code sent to your inbox
|
|
281
|
+
- Lost your secret? Use `POST /v1/auth/recover` to get a recovery device code emailed
|
|
282
|
+
- Rotate secrets via `POST /v1/apps/:id/rotate-secret` (auth required, sends notification)
|
|
283
|
+
|
|
284
|
+
### Period Filters
|
|
285
|
+
|
|
286
|
+
All metrics support `1h`, `24h`, `7d`, and `30d` time windows via htmx-powered buttons โ no page reload required.
|
|
287
|
+
|
|
83
288
|
## ๐ SSE Streaming Flow (AI-Native)
|
|
84
289
|
|
|
85
290
|
For AI agents that prefer a **conversational handshake**, BOTCHA offers **Server-Sent Events (SSE)** streaming:
|
|
@@ -352,6 +557,50 @@ You can use the library freely, report issues, and discuss features. To contribu
|
|
|
352
557
|
|
|
353
558
|
**See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for complete guidelines, solver code examples, agent setup instructions, and detailed workflows.**
|
|
354
559
|
|
|
560
|
+
## Server-Side Verification (for API Providers)
|
|
561
|
+
|
|
562
|
+
If you're building an API that accepts BOTCHA tokens from agents, use the verification SDKs:
|
|
563
|
+
|
|
564
|
+
### TypeScript (Express / Hono)
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
npm install @botcha/verify
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
import { botchaVerify } from '@botcha/verify/express';
|
|
572
|
+
|
|
573
|
+
app.use('/api', botchaVerify({
|
|
574
|
+
secret: process.env.BOTCHA_SECRET!,
|
|
575
|
+
audience: 'https://api.example.com',
|
|
576
|
+
}));
|
|
577
|
+
|
|
578
|
+
app.get('/api/protected', (req, res) => {
|
|
579
|
+
console.log('Solve time:', req.botcha?.solveTime);
|
|
580
|
+
res.json({ message: 'Welcome, verified agent!' });
|
|
581
|
+
});
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Python (FastAPI / Django)
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
pip install botcha-verify
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
from fastapi import FastAPI, Depends
|
|
592
|
+
from botcha_verify.fastapi import BotchaVerify
|
|
593
|
+
|
|
594
|
+
app = FastAPI()
|
|
595
|
+
botcha = BotchaVerify(secret='your-secret-key')
|
|
596
|
+
|
|
597
|
+
@app.get('/api/data')
|
|
598
|
+
async def get_data(token = Depends(botcha)):
|
|
599
|
+
return {"solve_time": token.solve_time}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
> **Docs:** See [`@botcha/verify` README](./packages/verify/README.md) and [`botcha-verify` README](./packages/python-verify/README.md) for full API reference, Hono middleware, Django middleware, revocation checking, and custom error handlers.
|
|
603
|
+
|
|
355
604
|
## Client SDK (for AI Agents)
|
|
356
605
|
|
|
357
606
|
If you're building an AI agent that needs to access BOTCHA-protected APIs, use the client SDK:
|
|
@@ -410,6 +659,22 @@ const answers = solveBotcha([123456, 789012]);
|
|
|
410
659
|
// Returns: ['a1b2c3d4', 'e5f6g7h8']
|
|
411
660
|
```
|
|
412
661
|
|
|
662
|
+
**Python SDK:**
|
|
663
|
+
```python
|
|
664
|
+
from botcha import BotchaClient, solve_botcha
|
|
665
|
+
|
|
666
|
+
# Option 1: Auto-solve with client
|
|
667
|
+
async with BotchaClient() as client:
|
|
668
|
+
response = await client.fetch("https://api.example.com/agent-only")
|
|
669
|
+
data = await response.json()
|
|
670
|
+
|
|
671
|
+
# Option 2: Manual solve
|
|
672
|
+
answers = solve_botcha([123456, 789012])
|
|
673
|
+
# Returns: ['a1b2c3d4', 'e5f6g7h8']
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
> **Note:** The Python SDK is available on [PyPI](https://pypi.org/project/botcha/): `pip install botcha`
|
|
677
|
+
|
|
413
678
|
## License
|
|
414
679
|
|
|
415
680
|
MIT ยฉ [Dupe](https://dupe.com)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, StreamSession, StreamEvent, Problem, VerifyResult, StreamChallengeOptions, } from './types.js';
|
|
2
|
-
import type { BotchaClientOptions, VerifyResponse } from './types.js';
|
|
1
|
+
export type { SpeedProblem, BotchaClientOptions, ChallengeResponse, StandardChallengeResponse, VerifyResponse, TokenResponse, StreamSession, StreamEvent, Problem, VerifyResult, StreamChallengeOptions, CreateAppResponse, VerifyEmailResponse, ResendVerificationResponse, RecoverAccountResponse, RotateSecretResponse, } from './types.js';
|
|
2
|
+
import type { BotchaClientOptions, VerifyResponse, CreateAppResponse, VerifyEmailResponse, ResendVerificationResponse, RecoverAccountResponse, RotateSecretResponse } from './types.js';
|
|
3
3
|
export { BotchaStreamClient } from './stream.js';
|
|
4
4
|
/**
|
|
5
5
|
* BOTCHA Client SDK for AI Agents
|
|
@@ -21,7 +21,10 @@ export declare class BotchaClient {
|
|
|
21
21
|
private agentIdentity;
|
|
22
22
|
private maxRetries;
|
|
23
23
|
private autoToken;
|
|
24
|
+
private appId?;
|
|
25
|
+
private opts;
|
|
24
26
|
private cachedToken;
|
|
27
|
+
private _refreshToken;
|
|
25
28
|
private tokenExpiresAt;
|
|
26
29
|
constructor(options?: BotchaClientOptions);
|
|
27
30
|
/**
|
|
@@ -34,12 +37,20 @@ export declare class BotchaClient {
|
|
|
34
37
|
/**
|
|
35
38
|
* Get a JWT token from the BOTCHA service using the token flow.
|
|
36
39
|
* Automatically solves the challenge and verifies to obtain a token.
|
|
37
|
-
* Token is cached until near expiry (refreshed at
|
|
40
|
+
* Token is cached until near expiry (refreshed at 4 minutes).
|
|
38
41
|
*
|
|
39
42
|
* @returns JWT token string
|
|
40
43
|
* @throws Error if token acquisition fails
|
|
41
44
|
*/
|
|
42
45
|
getToken(): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Refresh the access token using the refresh token.
|
|
48
|
+
* Only works if a refresh token was obtained from a previous getToken() call.
|
|
49
|
+
*
|
|
50
|
+
* @returns New JWT access token string
|
|
51
|
+
* @throws Error if refresh fails or no refresh token available
|
|
52
|
+
*/
|
|
53
|
+
refreshToken(): Promise<string>;
|
|
43
54
|
/**
|
|
44
55
|
* Clear the cached token, forcing a refresh on the next request
|
|
45
56
|
*/
|
|
@@ -76,6 +87,73 @@ export declare class BotchaClient {
|
|
|
76
87
|
* ```
|
|
77
88
|
*/
|
|
78
89
|
createHeaders(): Promise<Record<string, string>>;
|
|
90
|
+
/**
|
|
91
|
+
* Create a new BOTCHA app. Email is required.
|
|
92
|
+
*
|
|
93
|
+
* The returned `app_secret` is only shown once โ save it securely.
|
|
94
|
+
* A 6-digit verification code will be sent to the provided email.
|
|
95
|
+
*
|
|
96
|
+
* @param email - Email address for the app owner
|
|
97
|
+
* @returns App creation response including app_id and app_secret
|
|
98
|
+
* @throws Error if app creation fails
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const app = await client.createApp('agent@example.com');
|
|
103
|
+
* console.log(app.app_id); // 'app_abc123'
|
|
104
|
+
* console.log(app.app_secret); // 'sk_...' (save this!)
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
createApp(email: string): Promise<CreateAppResponse>;
|
|
108
|
+
/**
|
|
109
|
+
* Verify the email address for an app using the 6-digit code sent via email.
|
|
110
|
+
*
|
|
111
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
112
|
+
* @param code - The 6-digit verification code from the email
|
|
113
|
+
* @returns Verification response
|
|
114
|
+
* @throws Error if verification fails
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const result = await client.verifyEmail('123456');
|
|
119
|
+
* console.log(result.email_verified); // true
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
verifyEmail(code: string, appId?: string): Promise<VerifyEmailResponse>;
|
|
123
|
+
/**
|
|
124
|
+
* Resend the email verification code.
|
|
125
|
+
*
|
|
126
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
127
|
+
* @returns Response with success status
|
|
128
|
+
* @throws Error if resend fails
|
|
129
|
+
*/
|
|
130
|
+
resendVerification(appId?: string): Promise<ResendVerificationResponse>;
|
|
131
|
+
/**
|
|
132
|
+
* Request account recovery via verified email.
|
|
133
|
+
* Sends a device code to the registered email address.
|
|
134
|
+
*
|
|
135
|
+
* Anti-enumeration: always returns the same response shape
|
|
136
|
+
* whether or not the email exists.
|
|
137
|
+
*
|
|
138
|
+
* @param email - The email address associated with the app
|
|
139
|
+
* @returns Recovery response (always success for anti-enumeration)
|
|
140
|
+
*/
|
|
141
|
+
recoverAccount(email: string): Promise<RecoverAccountResponse>;
|
|
142
|
+
/**
|
|
143
|
+
* Rotate the app secret. Requires an active dashboard session (Bearer token).
|
|
144
|
+
* The old secret is immediately invalidated.
|
|
145
|
+
*
|
|
146
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
147
|
+
* @returns New app_secret (save it โ only shown once)
|
|
148
|
+
* @throws Error if rotation fails or auth is missing
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const result = await client.rotateSecret();
|
|
153
|
+
* console.log(result.app_secret); // 'sk_new_...' (save this!)
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
rotateSecret(appId?: string): Promise<RotateSecretResponse>;
|
|
79
157
|
}
|
|
80
158
|
/**
|
|
81
159
|
* Convenience function for one-off solves
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,EACd,aAAa,EACb,aAAa,EACb,WAAW,EACX,OAAO,EACP,YAAY,EACZ,sBAAsB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/client/index.ts"],"names":[],"mappings":"AAMA,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,EACd,aAAa,EACb,aAAa,EACb,WAAW,EACX,OAAO,EACP,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAEV,mBAAmB,EAGnB,cAAc,EAEd,iBAAiB,EACjB,mBAAmB,EACnB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAuB;gBAEjC,OAAO,GAAE,mBAAwB;IAS7C;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAMnC;;;;;;;OAOG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA2FjC;;;;;;OAMG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAkCrC;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IA+BlE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;IAsBpE;;;;;;;;;OASG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2F/D;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAoBtD;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2B1D;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAyB7E;;;;;;OAMG;IACG,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAwB7E;;;;;;;;;OASG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAoBpE;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA+BlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,eAAe,YAAY,CAAC"}
|
package/dist/lib/client/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
// SDK version - hardcoded since npm_package_version is unreliable when used as a library
|
|
3
|
-
const SDK_VERSION = '0.
|
|
3
|
+
const SDK_VERSION = '0.10.0';
|
|
4
4
|
// Export stream client
|
|
5
5
|
export { BotchaStreamClient } from './stream.js';
|
|
6
6
|
/**
|
|
@@ -23,13 +23,18 @@ export class BotchaClient {
|
|
|
23
23
|
agentIdentity;
|
|
24
24
|
maxRetries;
|
|
25
25
|
autoToken;
|
|
26
|
+
appId;
|
|
27
|
+
opts;
|
|
26
28
|
cachedToken = null;
|
|
29
|
+
_refreshToken = null;
|
|
27
30
|
tokenExpiresAt = null;
|
|
28
31
|
constructor(options = {}) {
|
|
32
|
+
this.opts = options;
|
|
29
33
|
this.baseUrl = options.baseUrl || 'https://botcha.ai';
|
|
30
34
|
this.agentIdentity = options.agentIdentity || `BotchaClient/${SDK_VERSION}`;
|
|
31
35
|
this.maxRetries = options.maxRetries || 3;
|
|
32
36
|
this.autoToken = options.autoToken !== undefined ? options.autoToken : true;
|
|
37
|
+
this.appId = options.appId;
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
35
40
|
* Solve a BOTCHA speed challenge
|
|
@@ -43,23 +48,26 @@ export class BotchaClient {
|
|
|
43
48
|
/**
|
|
44
49
|
* Get a JWT token from the BOTCHA service using the token flow.
|
|
45
50
|
* Automatically solves the challenge and verifies to obtain a token.
|
|
46
|
-
* Token is cached until near expiry (refreshed at
|
|
51
|
+
* Token is cached until near expiry (refreshed at 4 minutes).
|
|
47
52
|
*
|
|
48
53
|
* @returns JWT token string
|
|
49
54
|
* @throws Error if token acquisition fails
|
|
50
55
|
*/
|
|
51
56
|
async getToken() {
|
|
52
|
-
// Check if we have a valid cached token (refresh at
|
|
57
|
+
// Check if we have a valid cached token (refresh at 1 minute before expiry)
|
|
53
58
|
if (this.cachedToken && this.tokenExpiresAt) {
|
|
54
59
|
const now = Date.now();
|
|
55
60
|
const timeUntilExpiry = this.tokenExpiresAt - now;
|
|
56
|
-
const refreshThreshold =
|
|
61
|
+
const refreshThreshold = 1 * 60 * 1000; // 1 minute before expiry
|
|
57
62
|
if (timeUntilExpiry > refreshThreshold) {
|
|
58
63
|
return this.cachedToken;
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
// Step 1: Get challenge from GET /v1/token
|
|
62
|
-
const
|
|
67
|
+
const tokenUrl = this.appId
|
|
68
|
+
? `${this.baseUrl}/v1/token?app_id=${encodeURIComponent(this.appId)}`
|
|
69
|
+
: `${this.baseUrl}/v1/token`;
|
|
70
|
+
const challengeRes = await fetch(tokenUrl, {
|
|
63
71
|
headers: { 'User-Agent': this.agentIdentity },
|
|
64
72
|
});
|
|
65
73
|
if (!challengeRes.ok) {
|
|
@@ -76,27 +84,80 @@ export class BotchaClient {
|
|
|
76
84
|
}
|
|
77
85
|
const answers = this.solve(problems);
|
|
78
86
|
// Step 3: Submit solution to POST /v1/token/verify
|
|
87
|
+
const verifyBody = {
|
|
88
|
+
id: challengeData.challenge.id,
|
|
89
|
+
answers,
|
|
90
|
+
};
|
|
91
|
+
// Include audience if specified
|
|
92
|
+
if (this.opts.audience) {
|
|
93
|
+
verifyBody.audience = this.opts.audience;
|
|
94
|
+
}
|
|
95
|
+
// Include app_id if specified
|
|
96
|
+
if (this.appId) {
|
|
97
|
+
verifyBody.app_id = this.appId;
|
|
98
|
+
}
|
|
79
99
|
const verifyRes = await fetch(`${this.baseUrl}/v1/token/verify`, {
|
|
80
100
|
method: 'POST',
|
|
81
101
|
headers: {
|
|
82
102
|
'Content-Type': 'application/json',
|
|
83
103
|
'User-Agent': this.agentIdentity,
|
|
84
104
|
},
|
|
85
|
-
body: JSON.stringify(
|
|
86
|
-
id: challengeData.challenge.id,
|
|
87
|
-
answers,
|
|
88
|
-
}),
|
|
105
|
+
body: JSON.stringify(verifyBody),
|
|
89
106
|
});
|
|
90
107
|
if (!verifyRes.ok) {
|
|
91
108
|
throw new Error(`Token verification failed with status ${verifyRes.status} ${verifyRes.statusText}`);
|
|
92
109
|
}
|
|
93
110
|
const verifyData = await verifyRes.json();
|
|
94
|
-
if (!verifyData.success
|
|
111
|
+
if (!verifyData.success && !verifyData.verified) {
|
|
112
|
+
throw new Error('Failed to obtain token from verification');
|
|
113
|
+
}
|
|
114
|
+
// Extract access token (prefer access_token field, fall back to token for backward compat)
|
|
115
|
+
const accessToken = verifyData.access_token || verifyData.token;
|
|
116
|
+
if (!accessToken) {
|
|
95
117
|
throw new Error('Failed to obtain token from verification');
|
|
96
118
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
119
|
+
// Store refresh token if provided
|
|
120
|
+
if (verifyData.refresh_token) {
|
|
121
|
+
this._refreshToken = verifyData.refresh_token;
|
|
122
|
+
}
|
|
123
|
+
// Cache the token - use expires_in from response (in seconds), default to 5 minutes
|
|
124
|
+
const expiresInSeconds = verifyData.expires_in || 300; // Default to 5 minutes
|
|
125
|
+
this.cachedToken = accessToken;
|
|
126
|
+
this.tokenExpiresAt = Date.now() + expiresInSeconds * 1000;
|
|
127
|
+
return this.cachedToken;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Refresh the access token using the refresh token.
|
|
131
|
+
* Only works if a refresh token was obtained from a previous getToken() call.
|
|
132
|
+
*
|
|
133
|
+
* @returns New JWT access token string
|
|
134
|
+
* @throws Error if refresh fails or no refresh token available
|
|
135
|
+
*/
|
|
136
|
+
async refreshToken() {
|
|
137
|
+
if (!this._refreshToken) {
|
|
138
|
+
throw new Error('No refresh token available. Call getToken() first.');
|
|
139
|
+
}
|
|
140
|
+
const refreshRes = await fetch(`${this.baseUrl}/v1/token/refresh`, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
'User-Agent': this.agentIdentity,
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
refresh_token: this._refreshToken,
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
if (!refreshRes.ok) {
|
|
151
|
+
throw new Error(`Token refresh failed with status ${refreshRes.status} ${refreshRes.statusText}`);
|
|
152
|
+
}
|
|
153
|
+
const refreshData = await refreshRes.json();
|
|
154
|
+
if (!refreshData.access_token) {
|
|
155
|
+
throw new Error('Failed to obtain access token from refresh');
|
|
156
|
+
}
|
|
157
|
+
// Update cached token with new access token
|
|
158
|
+
this.cachedToken = refreshData.access_token;
|
|
159
|
+
const expiresInSeconds = refreshData.expires_in || 300; // Default to 5 minutes
|
|
160
|
+
this.tokenExpiresAt = Date.now() + expiresInSeconds * 1000;
|
|
100
161
|
return this.cachedToken;
|
|
101
162
|
}
|
|
102
163
|
/**
|
|
@@ -104,13 +165,17 @@ export class BotchaClient {
|
|
|
104
165
|
*/
|
|
105
166
|
clearToken() {
|
|
106
167
|
this.cachedToken = null;
|
|
168
|
+
this._refreshToken = null;
|
|
107
169
|
this.tokenExpiresAt = null;
|
|
108
170
|
}
|
|
109
171
|
/**
|
|
110
172
|
* Get and solve a challenge from BOTCHA service
|
|
111
173
|
*/
|
|
112
174
|
async solveChallenge() {
|
|
113
|
-
const
|
|
175
|
+
const challengeUrl = this.appId
|
|
176
|
+
? `${this.baseUrl}/api/speed-challenge?app_id=${encodeURIComponent(this.appId)}`
|
|
177
|
+
: `${this.baseUrl}/api/speed-challenge`;
|
|
178
|
+
const res = await fetch(challengeUrl, {
|
|
114
179
|
headers: { 'User-Agent': this.agentIdentity },
|
|
115
180
|
});
|
|
116
181
|
if (!res.ok) {
|
|
@@ -180,16 +245,31 @@ export class BotchaClient {
|
|
|
180
245
|
...init,
|
|
181
246
|
headers,
|
|
182
247
|
});
|
|
183
|
-
// Handle 401 by
|
|
248
|
+
// Handle 401 by trying refresh first, then full re-verify if refresh fails
|
|
184
249
|
if (response.status === 401 && this.autoToken) {
|
|
185
|
-
this.clearToken();
|
|
186
250
|
try {
|
|
187
|
-
|
|
251
|
+
// Try refresh token first if available
|
|
252
|
+
let token;
|
|
253
|
+
if (this._refreshToken) {
|
|
254
|
+
try {
|
|
255
|
+
token = await this.refreshToken();
|
|
256
|
+
}
|
|
257
|
+
catch (refreshError) {
|
|
258
|
+
// Refresh failed, clear tokens and do full re-verify
|
|
259
|
+
this.clearToken();
|
|
260
|
+
token = await this.getToken();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// No refresh token, clear and do full re-verify
|
|
265
|
+
this.clearToken();
|
|
266
|
+
token = await this.getToken();
|
|
267
|
+
}
|
|
188
268
|
headers.set('Authorization', `Bearer ${token}`);
|
|
189
269
|
response = await fetch(url, { ...init, headers });
|
|
190
270
|
}
|
|
191
271
|
catch (error) {
|
|
192
|
-
// Token refresh failed, return the 401 response
|
|
272
|
+
// Token refresh/acquisition failed, return the 401 response
|
|
193
273
|
}
|
|
194
274
|
}
|
|
195
275
|
let retries = 0;
|
|
@@ -243,12 +323,176 @@ export class BotchaClient {
|
|
|
243
323
|
*/
|
|
244
324
|
async createHeaders() {
|
|
245
325
|
const { id, answers } = await this.solveChallenge();
|
|
246
|
-
|
|
326
|
+
const headers = {
|
|
247
327
|
'X-Botcha-Id': id,
|
|
248
328
|
'X-Botcha-Challenge-Id': id,
|
|
249
329
|
'X-Botcha-Answers': JSON.stringify(answers),
|
|
250
330
|
'User-Agent': this.agentIdentity,
|
|
251
331
|
};
|
|
332
|
+
// Include X-Botcha-App-Id header if appId is set
|
|
333
|
+
if (this.appId) {
|
|
334
|
+
headers['X-Botcha-App-Id'] = this.appId;
|
|
335
|
+
}
|
|
336
|
+
return headers;
|
|
337
|
+
}
|
|
338
|
+
// ============ APP MANAGEMENT ============
|
|
339
|
+
/**
|
|
340
|
+
* Create a new BOTCHA app. Email is required.
|
|
341
|
+
*
|
|
342
|
+
* The returned `app_secret` is only shown once โ save it securely.
|
|
343
|
+
* A 6-digit verification code will be sent to the provided email.
|
|
344
|
+
*
|
|
345
|
+
* @param email - Email address for the app owner
|
|
346
|
+
* @returns App creation response including app_id and app_secret
|
|
347
|
+
* @throws Error if app creation fails
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* const app = await client.createApp('agent@example.com');
|
|
352
|
+
* console.log(app.app_id); // 'app_abc123'
|
|
353
|
+
* console.log(app.app_secret); // 'sk_...' (save this!)
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
async createApp(email) {
|
|
357
|
+
const res = await fetch(`${this.baseUrl}/v1/apps`, {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
'User-Agent': this.agentIdentity,
|
|
362
|
+
},
|
|
363
|
+
body: JSON.stringify({ email }),
|
|
364
|
+
});
|
|
365
|
+
if (!res.ok) {
|
|
366
|
+
const body = await res.json().catch(() => ({}));
|
|
367
|
+
throw new Error(body.message || `App creation failed with status ${res.status}`);
|
|
368
|
+
}
|
|
369
|
+
const data = await res.json();
|
|
370
|
+
// Auto-set appId for subsequent requests
|
|
371
|
+
if (data.app_id) {
|
|
372
|
+
this.appId = data.app_id;
|
|
373
|
+
}
|
|
374
|
+
return data;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Verify the email address for an app using the 6-digit code sent via email.
|
|
378
|
+
*
|
|
379
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
380
|
+
* @param code - The 6-digit verification code from the email
|
|
381
|
+
* @returns Verification response
|
|
382
|
+
* @throws Error if verification fails
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```typescript
|
|
386
|
+
* const result = await client.verifyEmail('123456');
|
|
387
|
+
* console.log(result.email_verified); // true
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
async verifyEmail(code, appId) {
|
|
391
|
+
const id = appId || this.appId;
|
|
392
|
+
if (!id) {
|
|
393
|
+
throw new Error('No app ID. Call createApp() first or pass appId.');
|
|
394
|
+
}
|
|
395
|
+
const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/verify-email`, {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: {
|
|
398
|
+
'Content-Type': 'application/json',
|
|
399
|
+
'User-Agent': this.agentIdentity,
|
|
400
|
+
},
|
|
401
|
+
body: JSON.stringify({ code }),
|
|
402
|
+
});
|
|
403
|
+
if (!res.ok) {
|
|
404
|
+
const body = await res.json().catch(() => ({}));
|
|
405
|
+
throw new Error(body.message || `Email verification failed with status ${res.status}`);
|
|
406
|
+
}
|
|
407
|
+
return await res.json();
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Resend the email verification code.
|
|
411
|
+
*
|
|
412
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
413
|
+
* @returns Response with success status
|
|
414
|
+
* @throws Error if resend fails
|
|
415
|
+
*/
|
|
416
|
+
async resendVerification(appId) {
|
|
417
|
+
const id = appId || this.appId;
|
|
418
|
+
if (!id) {
|
|
419
|
+
throw new Error('No app ID. Call createApp() first or pass appId.');
|
|
420
|
+
}
|
|
421
|
+
const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/resend-verification`, {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
headers: {
|
|
424
|
+
'Content-Type': 'application/json',
|
|
425
|
+
'User-Agent': this.agentIdentity,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
if (!res.ok) {
|
|
429
|
+
const body = await res.json().catch(() => ({}));
|
|
430
|
+
throw new Error(body.message || `Resend verification failed with status ${res.status}`);
|
|
431
|
+
}
|
|
432
|
+
return await res.json();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Request account recovery via verified email.
|
|
436
|
+
* Sends a device code to the registered email address.
|
|
437
|
+
*
|
|
438
|
+
* Anti-enumeration: always returns the same response shape
|
|
439
|
+
* whether or not the email exists.
|
|
440
|
+
*
|
|
441
|
+
* @param email - The email address associated with the app
|
|
442
|
+
* @returns Recovery response (always success for anti-enumeration)
|
|
443
|
+
*/
|
|
444
|
+
async recoverAccount(email) {
|
|
445
|
+
const res = await fetch(`${this.baseUrl}/v1/auth/recover`, {
|
|
446
|
+
method: 'POST',
|
|
447
|
+
headers: {
|
|
448
|
+
'Content-Type': 'application/json',
|
|
449
|
+
'User-Agent': this.agentIdentity,
|
|
450
|
+
},
|
|
451
|
+
body: JSON.stringify({ email }),
|
|
452
|
+
});
|
|
453
|
+
if (!res.ok) {
|
|
454
|
+
const body = await res.json().catch(() => ({}));
|
|
455
|
+
throw new Error(body.message || `Account recovery failed with status ${res.status}`);
|
|
456
|
+
}
|
|
457
|
+
return await res.json();
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Rotate the app secret. Requires an active dashboard session (Bearer token).
|
|
461
|
+
* The old secret is immediately invalidated.
|
|
462
|
+
*
|
|
463
|
+
* @param appId - The app ID (defaults to the client's appId)
|
|
464
|
+
* @returns New app_secret (save it โ only shown once)
|
|
465
|
+
* @throws Error if rotation fails or auth is missing
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* const result = await client.rotateSecret();
|
|
470
|
+
* console.log(result.app_secret); // 'sk_new_...' (save this!)
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
async rotateSecret(appId) {
|
|
474
|
+
const id = appId || this.appId;
|
|
475
|
+
if (!id) {
|
|
476
|
+
throw new Error('No app ID. Call createApp() first or pass appId.');
|
|
477
|
+
}
|
|
478
|
+
// Rotate secret requires a dashboard session token
|
|
479
|
+
const headers = {
|
|
480
|
+
'Content-Type': 'application/json',
|
|
481
|
+
'User-Agent': this.agentIdentity,
|
|
482
|
+
};
|
|
483
|
+
// Use cached token if available (from dashboard auth)
|
|
484
|
+
if (this.cachedToken) {
|
|
485
|
+
headers['Authorization'] = `Bearer ${this.cachedToken}`;
|
|
486
|
+
}
|
|
487
|
+
const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(id)}/rotate-secret`, {
|
|
488
|
+
method: 'POST',
|
|
489
|
+
headers,
|
|
490
|
+
});
|
|
491
|
+
if (!res.ok) {
|
|
492
|
+
const body = await res.json().catch(() => ({}));
|
|
493
|
+
throw new Error(body.message || `Secret rotation failed with status ${res.status}`);
|
|
494
|
+
}
|
|
495
|
+
return await res.json();
|
|
252
496
|
}
|
|
253
497
|
}
|
|
254
498
|
/**
|
|
@@ -16,6 +16,10 @@ export interface BotchaClientOptions {
|
|
|
16
16
|
maxRetries?: number;
|
|
17
17
|
/** Enable automatic token acquisition and management (default: true) */
|
|
18
18
|
autoToken?: boolean;
|
|
19
|
+
/** Audience claim for token (optional) */
|
|
20
|
+
audience?: string;
|
|
21
|
+
/** Multi-tenant application ID (optional) */
|
|
22
|
+
appId?: string;
|
|
19
23
|
}
|
|
20
24
|
export interface ChallengeResponse {
|
|
21
25
|
success: boolean;
|
|
@@ -44,6 +48,10 @@ export interface VerifyResponse {
|
|
|
44
48
|
export interface TokenResponse {
|
|
45
49
|
success: boolean;
|
|
46
50
|
token: string | null;
|
|
51
|
+
access_token?: string;
|
|
52
|
+
refresh_token?: string;
|
|
53
|
+
expires_in?: number;
|
|
54
|
+
refresh_expires_in?: number;
|
|
47
55
|
expiresIn?: string;
|
|
48
56
|
challenge?: {
|
|
49
57
|
id: string;
|
|
@@ -52,6 +60,8 @@ export interface TokenResponse {
|
|
|
52
60
|
instructions: string;
|
|
53
61
|
};
|
|
54
62
|
nextStep?: string;
|
|
63
|
+
verified?: boolean;
|
|
64
|
+
solveTimeMs?: number;
|
|
55
65
|
}
|
|
56
66
|
/**
|
|
57
67
|
* Stream-related types for BotchaStreamClient
|
|
@@ -84,4 +94,41 @@ export interface StreamChallengeOptions {
|
|
|
84
94
|
/** Timeout for the full verification flow in milliseconds (default: 30000) */
|
|
85
95
|
timeout?: number;
|
|
86
96
|
}
|
|
97
|
+
export interface CreateAppResponse {
|
|
98
|
+
success: boolean;
|
|
99
|
+
app_id: string;
|
|
100
|
+
app_secret: string;
|
|
101
|
+
email: string;
|
|
102
|
+
email_verified: boolean;
|
|
103
|
+
verification_required: boolean;
|
|
104
|
+
warning: string;
|
|
105
|
+
credential_advice: string;
|
|
106
|
+
created_at: string;
|
|
107
|
+
rate_limit: number;
|
|
108
|
+
next_step: string;
|
|
109
|
+
}
|
|
110
|
+
export interface VerifyEmailResponse {
|
|
111
|
+
success: boolean;
|
|
112
|
+
email_verified?: boolean;
|
|
113
|
+
error?: string;
|
|
114
|
+
message?: string;
|
|
115
|
+
}
|
|
116
|
+
export interface ResendVerificationResponse {
|
|
117
|
+
success: boolean;
|
|
118
|
+
message?: string;
|
|
119
|
+
error?: string;
|
|
120
|
+
}
|
|
121
|
+
export interface RecoverAccountResponse {
|
|
122
|
+
success: boolean;
|
|
123
|
+
message: string;
|
|
124
|
+
}
|
|
125
|
+
export interface RotateSecretResponse {
|
|
126
|
+
success: boolean;
|
|
127
|
+
app_id?: string;
|
|
128
|
+
app_secret?: string;
|
|
129
|
+
warning?: string;
|
|
130
|
+
rotated_at?: string;
|
|
131
|
+
error?: string;
|
|
132
|
+
message?: string;
|
|
133
|
+
}
|
|
87
134
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/client/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/client/types.ts"],"names":[],"mappings":"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"}
|