@poppykit/client 0.4.0 → 0.5.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/API.md +3 -2
- package/AUTH.md +27 -2
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +6 -4
- package/DELIVERY.md +14 -1
- package/INTEGRATIONS.md +36 -6
- package/PROVIDER.md +9 -7
- package/README.md +11 -4
- package/RELEASING.md +1 -1
- package/STORAGE.md +15 -3
- package/dist/adapter-contract.js +1 -1
- package/dist/auth/client-auth.d.ts +10 -7
- package/dist/auth/client-auth.js +211 -83
- package/dist/auth/sqlite-store.js +16 -2
- package/dist/conversation.js +12 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/integrations.d.ts +6 -1
- package/dist/network/discovery.d.ts +20 -9
- package/dist/network/discovery.js +74 -21
- package/dist/network/redirect.d.ts +3 -0
- package/dist/network/redirect.js +38 -0
- package/dist/network/tasks.d.ts +23 -0
- package/dist/network/tasks.js +45 -0
- package/dist/postgres.js +14 -1
- package/dist/protocol/wire.d.ts +12 -11
- package/dist/protocol/wire.js +33 -7
- package/dist/provider.d.ts +2 -9
- package/dist/provider.js +15 -25
- package/examples/quickstart.mjs +1 -1
- package/package.json +2 -2
package/dist/provider.js
CHANGED
|
@@ -45,7 +45,6 @@ function https(value) { try {
|
|
|
45
45
|
catch {
|
|
46
46
|
return fail('binding_conflict');
|
|
47
47
|
} }
|
|
48
|
-
function onDomain(value, domain) { const host = https(value).hostname; return host === domain || host.endsWith(`.${domain}`); }
|
|
49
48
|
function publicKey(value) {
|
|
50
49
|
if (value.kty !== 'EC' || value.crv !== 'P-256' || typeof value.x !== 'string' || typeof value.y !== 'string' || 'd' in value || 'k' in value)
|
|
51
50
|
fail('unavailable');
|
|
@@ -67,6 +66,8 @@ export function createProvider(options) {
|
|
|
67
66
|
const row = tx.get('operation', `${enrollment.enrollmentId}:${operationId}`);
|
|
68
67
|
if (!row)
|
|
69
68
|
fail('binding_conflict');
|
|
69
|
+
if (row.binding.purpose === 'direct' && (typeof row.binding.sessionKey !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(row.binding.sessionKey)))
|
|
70
|
+
fail('binding_conflict');
|
|
70
71
|
if (row.expiresAt <= now())
|
|
71
72
|
fail('expired');
|
|
72
73
|
if (row.status === 'canceled')
|
|
@@ -138,37 +139,29 @@ export function createProvider(options) {
|
|
|
138
139
|
});
|
|
139
140
|
},
|
|
140
141
|
async begin(credential, input) {
|
|
141
|
-
exact(input, ['version', 'installationId', 'operationId', 'domain', 'issuer', 'purpose', 'sessionId', '
|
|
142
|
-
if (!['client-authentication', 'session', '
|
|
142
|
+
exact(input, ['version', 'installationId', 'operationId', 'domain', 'issuer', 'purpose', 'sessionId', 'sessionKey', 'state']);
|
|
143
|
+
if (!['client-authentication', 'session', 'direct'].includes(input.purpose))
|
|
143
144
|
fail('binding_conflict');
|
|
144
145
|
text(input.operationId);
|
|
145
146
|
if (input.sessionId !== undefined)
|
|
146
147
|
text(input.sessionId);
|
|
147
148
|
await authorizeCompany(credential, input);
|
|
148
|
-
if (input.purpose === 'client-authentication' &&
|
|
149
|
-
fail('binding_conflict');
|
|
150
|
-
if (input.purpose !== 'browser' && input.returnTo !== undefined)
|
|
149
|
+
if (input.purpose === 'client-authentication' && input.sessionId !== undefined)
|
|
151
150
|
fail('binding_conflict');
|
|
152
151
|
if (input.purpose !== 'direct' && input.state !== undefined)
|
|
153
152
|
fail('binding_conflict');
|
|
154
|
-
if (input.purpose === 'direct' && (typeof input.state !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(input.state) ||
|
|
153
|
+
if (input.purpose === 'direct' && (typeof input.state !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(input.state) || typeof input.sessionKey !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(input.sessionKey)))
|
|
155
154
|
fail('binding_conflict');
|
|
156
|
-
if (input.purpose
|
|
155
|
+
if (input.purpose !== 'direct' && input.sessionKey !== undefined)
|
|
157
156
|
fail('binding_conflict');
|
|
158
|
-
if (input.purpose === 'browser')
|
|
159
|
-
text(input.returnTo);
|
|
160
157
|
const metadata = await verified(text(input.domain), text(input.issuer));
|
|
161
|
-
if (input.purpose === 'browser' && (!onDomain(input.returnTo, metadata.document.organization.domain) || !metadata.document.web?.browser_session_endpoint))
|
|
162
|
-
fail('binding_conflict');
|
|
163
158
|
const identity = await store.transaction({ kind: 'identity', enrollmentId: enrollmentId(credential), issuer: input.issuer }, tx => {
|
|
164
159
|
const enrollment = auth(tx, credential, text(input.installationId));
|
|
165
160
|
if (!enrollment.allowedCompanies.includes(metadata.document.organization.domain))
|
|
166
161
|
fail('unauthorized');
|
|
167
162
|
return { enrollment, userId: user(tx, enrollment.visitor, input.issuer) };
|
|
168
163
|
});
|
|
169
|
-
|
|
170
|
-
fail('unauthorized');
|
|
171
|
-
const binding = { issuer: input.issuer, domain: metadata.document.organization.domain, purpose: input.purpose, sessionId: input.sessionId ?? null, returnTo: input.returnTo ?? null, stateHash: input.state ? hash(input.state) : null, userId: identity.userId, browserEndpoint: metadata.document.web?.browser_session_endpoint ?? null };
|
|
164
|
+
const binding = { issuer: input.issuer, domain: metadata.document.organization.domain, purpose: input.purpose, sessionKey: input.sessionKey ?? null, sessionId: input.sessionId ?? null, stateHash: input.state ? hash(input.state) : null, userId: identity.userId };
|
|
172
165
|
return store.transaction({ kind: 'begin', enrollmentId: enrollmentId(credential), operationId: input.operationId, issuer: input.issuer, ...(binding.stateHash ? { stateHash: binding.stateHash } : {}), at: now() }, tx => {
|
|
173
166
|
const enrollment = auth(tx, credential, text(input.installationId)), key = `${enrollment.enrollmentId}:${text(input.operationId)}`, existing = tx.get('operation', key);
|
|
174
167
|
if (existing) {
|
|
@@ -203,19 +196,16 @@ export function createProvider(options) {
|
|
|
203
196
|
exact(input, ['version', 'installationId', 'operationId', 'purpose']);
|
|
204
197
|
const snapshot = await store.transaction(selectedOperation(credential, input), tx => {
|
|
205
198
|
const enrollment = auth(tx, credential, text(input.installationId)), op = operation(tx, enrollment, text(input.operationId));
|
|
206
|
-
if (op.binding.purpose !== input.purpose || !['client-authentication', 'session'
|
|
199
|
+
if (op.binding.purpose !== input.purpose || !['client-authentication', 'session'].includes(input.purpose))
|
|
207
200
|
fail('binding_conflict');
|
|
208
201
|
throttle(tx, enrollment);
|
|
209
202
|
return { enrollment, op };
|
|
210
203
|
});
|
|
211
|
-
const b = snapshot.op.binding
|
|
212
|
-
|
|
213
|
-
fail('binding_conflict');
|
|
214
|
-
if (b.purpose === 'browser' && !await options.verifySession?.({ clientId: options.clientId, userId: b.userId, issuer: b.issuer, sessionId: b.sessionId }))
|
|
215
|
-
fail('unauthorized');
|
|
204
|
+
const b = snapshot.op.binding;
|
|
205
|
+
await verified(b.domain, b.issuer);
|
|
216
206
|
const iat = Math.floor(now() / 1000), exp = iat + 60;
|
|
217
|
-
const claims = { iss: options.clientId, sub: b.purpose === 'client-authentication' ? options.clientId : b.userId, aud: b.
|
|
218
|
-
const header = { alg: 'ES256', kid: options.signer.kid, typ: b.purpose === 'client-authentication' ? 'client-authentication+jwt' :
|
|
207
|
+
const claims = { iss: options.clientId, sub: b.purpose === 'client-authentication' ? options.clientId : b.userId, aud: b.issuer, iat, exp, jti: id() };
|
|
208
|
+
const header = { alg: 'ES256', kid: options.signer.kid, typ: b.purpose === 'client-authentication' ? 'client-authentication+jwt' : 'JWT' };
|
|
219
209
|
const unsigned = `${Buffer.from(JSON.stringify(header)).toString('base64url')}.${Buffer.from(JSON.stringify(claims)).toString('base64url')}`;
|
|
220
210
|
const signature = await options.signer.sign(new TextEncoder().encode(unsigned));
|
|
221
211
|
if (signature.length !== 64)
|
|
@@ -252,10 +242,10 @@ export function createProvider(options) {
|
|
|
252
242
|
});
|
|
253
243
|
},
|
|
254
244
|
async takeResult(credential, input) {
|
|
255
|
-
exact(input, ['version', 'installationId', 'operationId', 'issuer', 'sessionId']);
|
|
245
|
+
exact(input, ['version', 'installationId', 'operationId', 'issuer', 'sessionId', 'sessionKey']);
|
|
256
246
|
return store.transaction(selectedOperation(credential, input), tx => {
|
|
257
247
|
const enrollment = auth(tx, credential, text(input.installationId)), op = operation(tx, enrollment, text(input.operationId));
|
|
258
|
-
if (op.binding.issuer !== input.issuer || op.binding.
|
|
248
|
+
if (op.binding.issuer !== input.issuer || op.binding.sessionKey !== input.sessionKey || op.binding.purpose !== 'direct')
|
|
259
249
|
fail('binding_conflict');
|
|
260
250
|
if (op.status !== 'ready')
|
|
261
251
|
return { status: 'pending' };
|
package/examples/quickstart.mjs
CHANGED
|
@@ -7,7 +7,7 @@ if (!domain) throw new Error('usage: node examples/quickstart.mjs DOMAIN|--self-
|
|
|
7
7
|
const transport = selfCheck ? {
|
|
8
8
|
async fetch(request) {
|
|
9
9
|
assert.equal(request.url, 'https://company.example/.well-known/poppy.json');
|
|
10
|
-
return Response.json({ protocol_version: '0.
|
|
10
|
+
return Response.json({ protocol_version: '0.3', organization: { name: 'Example Company', domain }, web: {} });
|
|
11
11
|
},
|
|
12
12
|
} : createNodeTransport();
|
|
13
13
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poppykit/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Personal Agent Protocol client SDK",
|
|
6
6
|
"repository": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=24.
|
|
12
|
+
"node": ">=24.21.0 <25"
|
|
13
13
|
},
|
|
14
14
|
"packageManager": "pnpm@11.25.0",
|
|
15
15
|
"scripts": {
|