@agentvisa/verify 0.1.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 +178 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +44 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +74 -0
- package/dist/middleware.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +33 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +108 -0
- package/dist/verify.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @agentvisa/verify
|
|
2
|
+
|
|
3
|
+
Verify human-authorized AI agents in one call. Drop into any Node.js backend — Express, Fastify, Next.js, or plain `fetch`. Supports [Web Bot Auth (RFC 9421)](https://blog.cloudflare.com/web-bot-auth/) via the `AgentVisa-Assertion` header.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @agentvisa/verify
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The two-layer model
|
|
12
|
+
|
|
13
|
+
Cloudflare's [Web Bot Auth](https://blog.cloudflare.com/web-bot-auth/) tells you **which company's agent** is making a request — operator identity, cryptographically signed. It stops there by design. AgentVisa adds the missing half:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Layer 1 — Web Bot Auth (Cloudflare)
|
|
17
|
+
"This request is from Acme Corp's agent — verified."
|
|
18
|
+
|
|
19
|
+
Layer 2 — AgentVisa (this library)
|
|
20
|
+
"A real human (Face ID, just now) authorized that agent to act."
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
When both are present, the `AgentVisa-Assertion` token is covered by the RFC 9421 signature — operator identity and human authorization cryptographically bound in one request.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
Get your `widgetId` and `apiKey` from your [AgentVisa dashboard](https://agentvisa.ai/dashboard/vendor).
|
|
30
|
+
|
|
31
|
+
### Express middleware
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import express from 'express';
|
|
35
|
+
import { agentVisa } from '@agentvisa/verify';
|
|
36
|
+
|
|
37
|
+
const app = express();
|
|
38
|
+
|
|
39
|
+
// Reject any request without a valid AgentVisa token
|
|
40
|
+
app.use('/api', agentVisa({
|
|
41
|
+
widgetId: process.env.AGENTVISA_WIDGET_ID!,
|
|
42
|
+
apiKey: process.env.AGENTVISA_API_KEY!,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
app.post('/api/checkout', (req, res) => {
|
|
46
|
+
// req.agentVisa is populated — token is valid
|
|
47
|
+
res.json({ ok: true });
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Flag mode (decide in your handler)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
app.use(agentVisa({
|
|
55
|
+
widgetId: process.env.AGENTVISA_WIDGET_ID!,
|
|
56
|
+
apiKey: process.env.AGENTVISA_API_KEY!,
|
|
57
|
+
onFail: 'flag', // don't auto-reject — let the handler decide
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
app.post('/api/order', (req, res) => {
|
|
61
|
+
if (!req.agentVisa?.valid) {
|
|
62
|
+
// Downgrade: show a CAPTCHA, require extra confirmation, etc.
|
|
63
|
+
return res.status(401).json({ error: 'human_required' });
|
|
64
|
+
}
|
|
65
|
+
// Full trust path
|
|
66
|
+
res.json({ ok: true });
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Standalone (any framework)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { extractToken, verify } from '@agentvisa/verify';
|
|
74
|
+
|
|
75
|
+
// Works with any framework — Next.js, Fastify, plain Node http, etc.
|
|
76
|
+
async function handler(req, res) {
|
|
77
|
+
const { token } = extractToken(req.headers);
|
|
78
|
+
if (!token) return res.status(401).json({ error: 'agentvisa_required' });
|
|
79
|
+
|
|
80
|
+
const result = await verify(token, {
|
|
81
|
+
widgetId: process.env.AGENTVISA_WIDGET_ID!,
|
|
82
|
+
apiKey: process.env.AGENTVISA_API_KEY!,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (!result.valid) return res.status(401).json({ error: result.reason });
|
|
86
|
+
// Proceed
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Web Bot Auth (RFC 9421)
|
|
91
|
+
|
|
92
|
+
When an agent sends `AgentVisa-Assertion` instead of `X-AgentVisa-Token`, the library detects that the token was bound in a Web Bot Auth signature and reports it:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
app.post('/api/action', (req, res) => {
|
|
96
|
+
const av = req.agentVisa!;
|
|
97
|
+
|
|
98
|
+
console.log(av.valid); // true — human verified
|
|
99
|
+
console.log(av.webBotAuthBound); // true — token covered by RFC 9421 signature
|
|
100
|
+
// false — arrived via X-AgentVisa-Token (standard)
|
|
101
|
+
|
|
102
|
+
// Both true = complete trust: which agent + which human, cryptographically bound
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> **Note:** `webBotAuthBound: true` confirms the structural binding is present. The RFC 9421 signature cryptography is verified by your WAF/CDN (e.g. Cloudflare Web Bot Auth) before the request reaches your app.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## API reference
|
|
111
|
+
|
|
112
|
+
### `agentVisa(options)` — Express middleware
|
|
113
|
+
|
|
114
|
+
| Option | Type | Required | Description |
|
|
115
|
+
|--------|------|----------|-------------|
|
|
116
|
+
| `widgetId` | `string` | ✓ | Your `wgt_xxx` widget ID |
|
|
117
|
+
| `apiKey` | `string` | ✓ | Your `wk_xxx` API key |
|
|
118
|
+
| `onFail` | `'reject' \| 'flag'` | — | `'reject'` (default) sends 401; `'flag'` sets `req.agentVisa` and continues |
|
|
119
|
+
| `rejectBody` | `object` | — | Custom body for 401 responses |
|
|
120
|
+
| `apiUrl` | `string` | — | Override API base URL (testing) |
|
|
121
|
+
|
|
122
|
+
Attaches `req.agentVisa: VerifyResult & { token: string \| null }` to the request.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `verify(token, options, headers?)` — standalone
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const result = await verify(token, { widgetId, apiKey }, req.headers);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Returns a `VerifyResult`:
|
|
133
|
+
|
|
134
|
+
| Field | Type | Description |
|
|
135
|
+
|-------|------|-------------|
|
|
136
|
+
| `valid` | `boolean` | Token is valid and human is verified |
|
|
137
|
+
| `humanVerified` | `boolean` | Alias for `valid` |
|
|
138
|
+
| `reason` | `string` | `'ok'` or error code |
|
|
139
|
+
| `verifiedAt` | `string` | ISO timestamp of human verification |
|
|
140
|
+
| `expiresAt` | `string` | ISO timestamp of token expiry |
|
|
141
|
+
| `domainVerified` | `boolean` | Requesting domain is verified by the Widget Holder |
|
|
142
|
+
| `webBotAuthBound` | `boolean` | Token was covered by RFC 9421 Signature-Input |
|
|
143
|
+
| `raw` | `object` | Raw AgentVisa API response |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### `extractToken(headers)` — utility
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const { token, source } = extractToken(req.headers);
|
|
151
|
+
// source: 'assertion' | 'header' | null
|
|
152
|
+
// 'assertion' = AgentVisa-Assertion header (Web Bot Auth mode)
|
|
153
|
+
// 'header' = X-AgentVisa-Token header (standard mode)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## How agents send the token
|
|
159
|
+
|
|
160
|
+
Agents use the [AgentVisa MCP server](https://github.com/AgentVisa-ai/agentvisa/tree/main/mcp) (`@agentvisa/mcp`) to get a `tmp_xxx` token, then send it in one of two ways:
|
|
161
|
+
|
|
162
|
+
**Standard mode:**
|
|
163
|
+
```
|
|
164
|
+
X-AgentVisa-Token: tmp_xxxxxxxxxxxxxxxxxxxx
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Web Bot Auth mode** (RFC 9421 — token bound in the signature):
|
|
168
|
+
```
|
|
169
|
+
AgentVisa-Assertion: tmp_xxxxxxxxxxxxxxxxxxxx
|
|
170
|
+
Signature-Input: sig1=("@method" "@path" "host" "agentvisa-assertion");keyid="acme-bot-key";created=1234567890
|
|
171
|
+
Signature: sig1=:base64sig:
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT · [agentvisa.ai](https://agentvisa.ai)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify
|
|
3
|
+
*
|
|
4
|
+
* Verify human-authorized AI agents in one call.
|
|
5
|
+
* Works standalone or as Express middleware.
|
|
6
|
+
* Supports Web Bot Auth (RFC 9421) via AgentVisa-Assertion header.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Standalone
|
|
10
|
+
* import { extractToken, verify } from '@agentvisa/verify';
|
|
11
|
+
* const { token } = extractToken(req.headers);
|
|
12
|
+
* const result = await verify(token, { widgetId, apiKey });
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Express middleware
|
|
16
|
+
* import { agentVisa } from '@agentvisa/verify';
|
|
17
|
+
* app.use('/api', agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
18
|
+
*/
|
|
19
|
+
export { extractToken, verify } from './verify.js';
|
|
20
|
+
export { agentVisa } from './middleware.js';
|
|
21
|
+
export type { VerifyOptions, VerifyResult, MiddlewareOptions } from './types.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify
|
|
3
|
+
*
|
|
4
|
+
* Verify human-authorized AI agents in one call.
|
|
5
|
+
* Works standalone or as Express middleware.
|
|
6
|
+
* Supports Web Bot Auth (RFC 9421) via AgentVisa-Assertion header.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Standalone
|
|
10
|
+
* import { extractToken, verify } from '@agentvisa/verify';
|
|
11
|
+
* const { token } = extractToken(req.headers);
|
|
12
|
+
* const result = await verify(token, { widgetId, apiKey });
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Express middleware
|
|
16
|
+
* import { agentVisa } from '@agentvisa/verify';
|
|
17
|
+
* app.use('/api', agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
18
|
+
*/
|
|
19
|
+
export { extractToken, verify } from './verify.js';
|
|
20
|
+
export { agentVisa } from './middleware.js';
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify — Express middleware
|
|
3
|
+
*/
|
|
4
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
5
|
+
import type { MiddlewareOptions, VerifyResult } from './types.js';
|
|
6
|
+
declare global {
|
|
7
|
+
namespace Express {
|
|
8
|
+
interface Request {
|
|
9
|
+
agentVisa?: VerifyResult & {
|
|
10
|
+
token: string | null;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Express middleware that verifies an incoming AgentVisa token.
|
|
17
|
+
*
|
|
18
|
+
* Reads the token from AgentVisa-Assertion (Web Bot Auth mode) or
|
|
19
|
+
* X-AgentVisa-Token (standard mode), calls the AgentVisa API, and
|
|
20
|
+
* either rejects the request (401) or attaches the result to req.agentVisa.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Reject unverified agents immediately
|
|
24
|
+
* app.use('/api', agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Flag and decide in your handler
|
|
28
|
+
* app.use(agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx', onFail: 'flag' }));
|
|
29
|
+
* app.post('/checkout', (req, res) => {
|
|
30
|
+
* if (!req.agentVisa?.valid) return res.status(401).json({ error: 'human_required' });
|
|
31
|
+
* // ... proceed
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Web Bot Auth — check binding too
|
|
36
|
+
* app.use(agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
37
|
+
* app.post('/api', (req, res) => {
|
|
38
|
+
* const av = req.agentVisa!;
|
|
39
|
+
* // av.valid → human verified
|
|
40
|
+
* // av.webBotAuthBound → token was bound in RFC 9421 signature
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
export declare function agentVisa(options: MiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
44
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGlE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,SAAS,CAAC,EAAE,YAAY,GAAG;gBAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;aAAE,CAAC;SACrD;KACF;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,iBAAiB,IAIhD,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CA+CjB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify — Express middleware
|
|
3
|
+
*/
|
|
4
|
+
import { extractToken, verify } from './verify.js';
|
|
5
|
+
/**
|
|
6
|
+
* Express middleware that verifies an incoming AgentVisa token.
|
|
7
|
+
*
|
|
8
|
+
* Reads the token from AgentVisa-Assertion (Web Bot Auth mode) or
|
|
9
|
+
* X-AgentVisa-Token (standard mode), calls the AgentVisa API, and
|
|
10
|
+
* either rejects the request (401) or attaches the result to req.agentVisa.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Reject unverified agents immediately
|
|
14
|
+
* app.use('/api', agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Flag and decide in your handler
|
|
18
|
+
* app.use(agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx', onFail: 'flag' }));
|
|
19
|
+
* app.post('/checkout', (req, res) => {
|
|
20
|
+
* if (!req.agentVisa?.valid) return res.status(401).json({ error: 'human_required' });
|
|
21
|
+
* // ... proceed
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Web Bot Auth — check binding too
|
|
26
|
+
* app.use(agentVisa({ widgetId: 'wgt_xxx', apiKey: 'wk_xxx' }));
|
|
27
|
+
* app.post('/api', (req, res) => {
|
|
28
|
+
* const av = req.agentVisa!;
|
|
29
|
+
* // av.valid → human verified
|
|
30
|
+
* // av.webBotAuthBound → token was bound in RFC 9421 signature
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
export function agentVisa(options) {
|
|
34
|
+
const { onFail = 'reject', rejectBody, ...verifyOptions } = options;
|
|
35
|
+
return async function agentVisaMiddleware(req, res, next) {
|
|
36
|
+
const headers = req.headers;
|
|
37
|
+
const { token } = extractToken(headers);
|
|
38
|
+
if (!token) {
|
|
39
|
+
if (onFail === 'flag') {
|
|
40
|
+
req.agentVisa = {
|
|
41
|
+
valid: false,
|
|
42
|
+
humanVerified: false,
|
|
43
|
+
reason: 'no_token',
|
|
44
|
+
token: null,
|
|
45
|
+
};
|
|
46
|
+
next();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
res.status(401).json(rejectBody ?? {
|
|
50
|
+
error: 'agentvisa_required',
|
|
51
|
+
message: 'This endpoint requires an AgentVisa token. ' +
|
|
52
|
+
'Set AgentVisa-Assertion (Web Bot Auth) or X-AgentVisa-Token header.',
|
|
53
|
+
widget_id: verifyOptions.widgetId,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const result = await verify(token, verifyOptions, headers);
|
|
58
|
+
req.agentVisa = { ...result, token };
|
|
59
|
+
if (!result.valid) {
|
|
60
|
+
if (onFail === 'flag') {
|
|
61
|
+
next();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
res.status(401).json(rejectBody ?? {
|
|
65
|
+
error: 'agentvisa_invalid',
|
|
66
|
+
reason: result.reason,
|
|
67
|
+
message: 'AgentVisa verification failed.',
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
next();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAYnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,SAAS,CAAC,OAA0B;IAClD,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IAEpE,OAAO,KAAK,UAAU,mBAAmB,CACvC,GAAY,EACZ,GAAa,EACb,IAAkB;QAElB,MAAM,OAAO,GAAG,GAAG,CAAC,OAAwD,CAAC;QAC7E,MAAM,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAExC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,GAAG,CAAC,SAAS,GAAG;oBACd,KAAK,EAAE,KAAK;oBACZ,aAAa,EAAE,KAAK;oBACpB,MAAM,EAAE,UAAU;oBAClB,KAAK,EAAE,IAAI;iBACZ,CAAC;gBACF,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAClB,UAAU,IAAI;gBACZ,KAAK,EAAE,oBAAoB;gBAC3B,OAAO,EACL,6CAA6C;oBAC7C,qEAAqE;gBACvE,SAAS,EAAE,aAAa,CAAC,QAAQ;aAClC,CACF,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;QAC3D,GAAG,CAAC,SAAS,GAAG,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC;QAErC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAClB,UAAU,IAAI;gBACZ,KAAK,EAAE,mBAAmB;gBAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,gCAAgC;aAC1C,CACF,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify — TypeScript types
|
|
3
|
+
*/
|
|
4
|
+
export interface VerifyOptions {
|
|
5
|
+
/** Widget ID from your AgentVisa dashboard (wgt_xxx) */
|
|
6
|
+
widgetId: string;
|
|
7
|
+
/** Widget API key from your AgentVisa dashboard (wk_xxx) */
|
|
8
|
+
apiKey: string;
|
|
9
|
+
/** Override the AgentVisa API base URL (default: https://api.agentvisa.ai) */
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface VerifyResult {
|
|
13
|
+
/** Whether the AgentVisa token is valid */
|
|
14
|
+
valid: boolean;
|
|
15
|
+
/** Whether human verification passed (same as valid — future-proofing) */
|
|
16
|
+
humanVerified: boolean;
|
|
17
|
+
/** Reason code from AgentVisa API, or an internal error code */
|
|
18
|
+
reason: string;
|
|
19
|
+
/** ISO timestamp of when the human was verified */
|
|
20
|
+
verifiedAt?: string;
|
|
21
|
+
/** ISO timestamp of when this token expires */
|
|
22
|
+
expiresAt?: string;
|
|
23
|
+
/** Whether the requesting domain is verified by the Widget Holder */
|
|
24
|
+
domainVerified?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the AgentVisa-Assertion header was structurally bound in a
|
|
27
|
+
* Web Bot Auth (RFC 9421) Signature-Input.
|
|
28
|
+
*
|
|
29
|
+
* true = the token was covered by a Web Bot Auth signature (binding is present).
|
|
30
|
+
* The WAF/CDN (e.g. Cloudflare) is responsible for verifying the signature itself.
|
|
31
|
+
* false = token arrived via X-AgentVisa-Token (standard, non-bound mode).
|
|
32
|
+
* undefined = header inspection was not performed.
|
|
33
|
+
*/
|
|
34
|
+
webBotAuthBound?: boolean;
|
|
35
|
+
/** Raw response body from the AgentVisa API */
|
|
36
|
+
raw?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface MiddlewareOptions extends VerifyOptions {
|
|
39
|
+
/**
|
|
40
|
+
* What to do when verification fails:
|
|
41
|
+
* 'reject' — respond 401 immediately (default)
|
|
42
|
+
* 'flag' — set req.agentVisa and continue; let your handler decide
|
|
43
|
+
*/
|
|
44
|
+
onFail?: 'reject' | 'flag';
|
|
45
|
+
/** Custom 401 response body when onFail = 'reject' */
|
|
46
|
+
rejectBody?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,aAAa,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify — core verification logic
|
|
3
|
+
*/
|
|
4
|
+
import type { VerifyOptions, VerifyResult } from './types.js';
|
|
5
|
+
type Headers = Record<string, string | string[] | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Extract the AgentVisa token from incoming request headers.
|
|
8
|
+
*
|
|
9
|
+
* Checks in order:
|
|
10
|
+
* 1. AgentVisa-Assertion — Web Bot Auth mode (token bound in RFC 9421 signature)
|
|
11
|
+
* 2. X-AgentVisa-Token — Standard mode
|
|
12
|
+
*
|
|
13
|
+
* Returns the token and which header it came from.
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractToken(headers: Headers): {
|
|
16
|
+
token: string | null;
|
|
17
|
+
source: 'assertion' | 'header' | null;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Verify an AgentVisa temporary token against the AgentVisa API.
|
|
21
|
+
*
|
|
22
|
+
* @param token The tmp_xxx token from the agent's request header
|
|
23
|
+
* @param options widgetId, apiKey, and optional apiUrl override
|
|
24
|
+
* @param headers Full request headers — used to detect Web Bot Auth binding
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Standalone usage (any framework)
|
|
28
|
+
* const result = await verify(token, { widgetId, apiKey });
|
|
29
|
+
* if (!result.valid) return res.status(401).json({ error: result.reason });
|
|
30
|
+
*/
|
|
31
|
+
export declare function verify(token: string, options: VerifyOptions, headers?: Headers): Promise<VerifyResult>;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI9D,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAuB7D;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG;IAC9C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,CAAC;CACvC,CAQA;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,YAAY,CAAC,CAqDvB"}
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentvisa/verify — core verification logic
|
|
3
|
+
*/
|
|
4
|
+
const DEFAULT_API_URL = 'https://api.agentvisa.ai';
|
|
5
|
+
function headerValue(headers, name) {
|
|
6
|
+
const val = headers[name.toLowerCase()];
|
|
7
|
+
if (!val)
|
|
8
|
+
return undefined;
|
|
9
|
+
return Array.isArray(val) ? val[0] : val;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check whether the AgentVisa-Assertion header is covered by a
|
|
13
|
+
* Web Bot Auth (RFC 9421) Signature-Input.
|
|
14
|
+
*
|
|
15
|
+
* This is a structural check — confirms the token was included in the
|
|
16
|
+
* set of headers the agent signed, but does NOT verify the RFC 9421
|
|
17
|
+
* signature cryptography. Signature verification is handled by your
|
|
18
|
+
* WAF/CDN layer (e.g. Cloudflare Web Bot Auth).
|
|
19
|
+
*/
|
|
20
|
+
function isWebBotAuthBound(headers) {
|
|
21
|
+
const sigInput = headerValue(headers, 'signature-input');
|
|
22
|
+
if (!sigInput)
|
|
23
|
+
return false;
|
|
24
|
+
return sigInput.toLowerCase().includes('"agentvisa-assertion"');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Extract the AgentVisa token from incoming request headers.
|
|
28
|
+
*
|
|
29
|
+
* Checks in order:
|
|
30
|
+
* 1. AgentVisa-Assertion — Web Bot Auth mode (token bound in RFC 9421 signature)
|
|
31
|
+
* 2. X-AgentVisa-Token — Standard mode
|
|
32
|
+
*
|
|
33
|
+
* Returns the token and which header it came from.
|
|
34
|
+
*/
|
|
35
|
+
export function extractToken(headers) {
|
|
36
|
+
const assertion = headerValue(headers, 'agentvisa-assertion');
|
|
37
|
+
if (assertion)
|
|
38
|
+
return { token: assertion, source: 'assertion' };
|
|
39
|
+
const token = headerValue(headers, 'x-agentvisa-token');
|
|
40
|
+
if (token)
|
|
41
|
+
return { token, source: 'header' };
|
|
42
|
+
return { token: null, source: null };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify an AgentVisa temporary token against the AgentVisa API.
|
|
46
|
+
*
|
|
47
|
+
* @param token The tmp_xxx token from the agent's request header
|
|
48
|
+
* @param options widgetId, apiKey, and optional apiUrl override
|
|
49
|
+
* @param headers Full request headers — used to detect Web Bot Auth binding
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Standalone usage (any framework)
|
|
53
|
+
* const result = await verify(token, { widgetId, apiKey });
|
|
54
|
+
* if (!result.valid) return res.status(401).json({ error: result.reason });
|
|
55
|
+
*/
|
|
56
|
+
export async function verify(token, options, headers) {
|
|
57
|
+
const apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
|
|
58
|
+
const webBotAuthBound = headers ? isWebBotAuthBound(headers) : undefined;
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await fetch(`${apiUrl}/v1/verify`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
'X-Widget-Api-Key': options.apiKey,
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
token,
|
|
69
|
+
widget_id: options.widgetId,
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return {
|
|
75
|
+
valid: false,
|
|
76
|
+
humanVerified: false,
|
|
77
|
+
reason: 'network_error',
|
|
78
|
+
webBotAuthBound,
|
|
79
|
+
raw: { error: String(err) },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let data = {};
|
|
83
|
+
try {
|
|
84
|
+
data = (await response.json());
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Non-JSON response — treat as error
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
humanVerified: false,
|
|
91
|
+
reason: `http_${response.status}`,
|
|
92
|
+
webBotAuthBound,
|
|
93
|
+
raw: {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const valid = response.ok && data.valid === true;
|
|
97
|
+
return {
|
|
98
|
+
valid,
|
|
99
|
+
humanVerified: valid,
|
|
100
|
+
reason: data.reason ?? (response.ok ? 'ok' : `http_${response.status}`),
|
|
101
|
+
verifiedAt: data.verified_at,
|
|
102
|
+
expiresAt: data.expires_at,
|
|
103
|
+
domainVerified: data.domain_verified,
|
|
104
|
+
webBotAuthBound,
|
|
105
|
+
raw: data,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentvisa/verify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AgentVisa verifier — verify human-authorized AI agents in one call. Supports Web Bot Auth (RFC 9421) via AgentVisa-Assertion header.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentvisa",
|
|
7
|
+
"web-bot-auth",
|
|
8
|
+
"rfc9421",
|
|
9
|
+
"ai-agent",
|
|
10
|
+
"human-verification",
|
|
11
|
+
"express",
|
|
12
|
+
"middleware"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://agentvisa.ai",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/AgentVisa-ai/agentvisa.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/AgentVisa-ai/agentvisa/issues"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"dev": "tsc --watch",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"express": ">=4.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"express": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/express": "^4.17.0",
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"typescript": "^5.5.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|