@datalayer/personal-agent-protocol 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,52 +8,62 @@
8
8
 
9
9
  # 🤖 🌸 Personal Agent Protocol
10
10
 
11
- Python and TypeScript SDKs for [Personal Agent Protocol](https://personalagentprotocol.org),
12
- currently targeting [draft 0.1](https://personalagentprotocol.org/docs/spec).
13
-
14
- The first implemented slice provides:
15
-
16
- - forward-compatible discovery, OAuth metadata, client metadata, conversation
17
- messages/events, and Operations v1 models in Python and TypeScript;
18
- - company discovery at `/.well-known/poppy.json`;
19
- - validation of the requested company domain, OAuth issuer, and
20
- `poppy_domains` relationship;
21
- - HTTPS-only redirects, bounded response sizes, and a public-address preflight
22
- guard in the Python discovery client;
23
- - Reactor-backed protocol profiles, discovery transports, URL-safety policies,
24
- extension handlers, clocks, randomness, signing, protected keys, token
25
- storage, pairwise identity, caches, and browser-handoff contribution points
26
- in both SDKs;
27
- - redacted session-token values, random pairwise user-ID generation and atomic
28
- file-backed reference persistence, PAP 4.2 assertion claims, and JWT-bearer
29
- request forms;
30
- - signed-out session start and renewal over HTTP, with Reactor-provided signing
31
- and DPoP proof generation, in both SDKs;
32
- - asymmetric ES256/RS256 JOSE signers and RFC 9449 DPoP creation and
33
- verification, including nonce, request, token-hash, freshness, and replay
34
- checks, in both SDKs;
35
- - Direct Sign-In state, S256 PKCE, callback validation, and redacted exchange
36
- forms plus one-use browser and token-exchange orchestration in both SDKs;
37
- - duplicate JSON member rejection at Python and TypeScript trust boundaries;
38
- - a Reactor-extensible `pap` CLI with non-secret profiles, verified company
39
- discovery, extension diagnostics, versioned JSON, and a safe live-session
40
- discovery command;
41
- - one shared set of fixtures exercised by both SDKs.
42
-
43
- Managed key custody and durable replay storage, device/mediated sign-in,
44
- conversations, and the Operations client/server are not implemented yet.
45
- Direct Sign-In requires host-provided authorization-state, browser, signer, and
46
- DPoP contributions; the SDK does not open a browser or persist state through an
47
- implicit global service.
48
-
49
- Session requests fail closed unless the host contributes signer and
50
- DPoP providers backed by suitable key custody.
51
-
52
- ## Python
11
+ ![Personal Agent Protocol demo](https://images.datalayer.io/products/personal-agent-protocol/pap-demo.gif)
12
+
13
+ Python and TypeScript SDKs for the
14
+ [Personal Agent Protocol](https://personalagentprotocol.org), currently
15
+ targeting [draft 0.1](https://personalagentprotocol.org/docs/spec).
16
+
17
+ PAP lets a personal agent discover and talk to a company's agent on behalf of
18
+ a person. The company remains responsible for its identity, authorization,
19
+ data, and actions; the personal agent does not need the person's company
20
+ password or a copy of their account data.
21
+
22
+ ## Understand PAP in one minute
23
+
24
+ A typical interaction has five parts:
25
+
26
+ 1. **Discover** — the personal agent reads the company's public
27
+ `/.well-known/poppy.json` document.
28
+ 1. **Start a Session** — the two agents establish a pairwise, initially
29
+ signed-out relationship.
30
+ 1. **Converse** — the personal agent can ask questions without disclosing the
31
+ person's company identity.
32
+ 1. **Sign in when required** — the company opens its own authorization page.
33
+ Credentials remain between the person and the company.
34
+ 1. **Continue safely** — the same conversation resumes with proof-bound access
35
+ and the scopes the person approved.
36
+
37
+ ```mermaid
38
+ sequenceDiagram
39
+ actor Person
40
+ participant PA as Personal agent
41
+ participant Company as Company agent
42
+ participant Auth as Company authorization server
43
+
44
+ PA->>Company: Discover /.well-known/poppy.json
45
+ Company-->>PA: Agent, Session, and authorization endpoints
46
+ PA->>Auth: Start a signed-out Session
47
+ Auth-->>PA: DPoP-bound Session Token
48
+ PA->>Company: Start a conversation
49
+ Company-->>PA: Sign-in required for account data
50
+ PA-->>Person: Open the company's sign-in page
51
+ Person->>Auth: Sign in and approve scopes
52
+ Auth-->>PA: Signed-in Session
53
+ PA->>Company: Continue the same conversation
54
+ Company-->>PA: Account-aware response
55
+ ```
56
+
57
+ The SDKs implement these protocol and security mechanics. They do not decide
58
+ what an agent should say, which model it should use, or whether a proposed
59
+ action is appropriate for a person.
60
+
61
+ ## Install and discover a company
62
+
63
+ ### Python
53
64
 
54
65
  ```bash
55
- python -m pip install -e '.[test,lint,typing]'
56
- python -m pytest tests
66
+ python -m pip install personal-agent-protocol
57
67
  ```
58
68
 
59
69
  ```python
@@ -66,28 +76,159 @@ async def main() -> None:
66
76
  async with DiscoveryClient() as client:
67
77
  company = await client.discover("example.com")
68
78
  print(company.document.organization.name)
79
+ print(company.document.agent.protocols)
69
80
 
70
81
 
71
82
  asyncio.run(main())
72
83
  ```
73
84
 
74
- `DiscoveryClient(reactor=platform)` accepts an existing Reactor
75
- `PluginPlatform`. The default `build_pap_reactor()` host contributes the HTTPX
76
- transport, public-network safety policy, and PAP 0.1 profile. A plugin can
77
- replace a default by contributing at a lower order; equal highest priorities
78
- fail closed as ambiguous.
85
+ ### TypeScript
86
+
87
+ ```bash
88
+ npm install @datalayer/personal-agent-protocol
89
+ ```
90
+
91
+ ```typescript
92
+ import { discoverCompany } from '@datalayer/personal-agent-protocol';
93
+
94
+ const company = await discoverCompany('example.com');
95
+ console.log(company.document.organization.name);
96
+ console.log(company.document.agent.protocols);
97
+ ```
98
+
99
+ Discovery is deliberately a small first example. Production Session,
100
+ sign-in, and conversation flows also require host-owned signing keys, DPoP
101
+ proof generation, token storage, and browser handoff. Those capabilities are
102
+ provided through Reactor rather than hidden global state.
103
+
104
+ ## Reactor is the extension foundation
105
+
106
+ Both SDKs are built on
107
+ [Datalayer Reactor](https://github.com/datalayer/reactor), a typed plugin and
108
+ contribution system. An application assembles a PAP host from explicit
109
+ contributions for:
110
+
111
+ - discovery transport and URL-safety policy;
112
+ - protocol profiles and extension handlers;
113
+ - clocks and cryptographic randomness;
114
+ - signing, protected keys, and DPoP proofs;
115
+ - pairwise identity, token storage, and caching;
116
+ - browser handoff and authorization-state storage;
117
+ - CLI commands supplied by PAP or third-party extensions.
118
+
119
+ This architecture keeps the protocol core portable while allowing a desktop
120
+ application, browser application, server, or test harness to provide the
121
+ security and platform behavior appropriate to its environment. Missing or
122
+ ambiguous security contributions fail closed.
123
+
124
+ Python accepts a host-assembled Reactor `PluginPlatform`:
125
+
126
+ ```python
127
+ from reactor import PluginManifest
128
+
129
+ from personal_agent_protocol import DiscoveryClient, build_pap_reactor
130
+
131
+
132
+ async def discover_with_host() -> None:
133
+ platform, owned_http = build_pap_reactor(
134
+ plugins=[
135
+ (PluginManifest(name="my-security", version="1"), my_security_plugin)
136
+ ]
137
+ )
138
+ try:
139
+ async with DiscoveryClient(reactor=platform) as client:
140
+ company = await client.discover("example.com")
141
+ print(company.document.organization.name)
142
+ finally:
143
+ platform.stop()
144
+ if owned_http is not None:
145
+ await owned_http.aclose()
146
+ ```
147
+
148
+ TypeScript can build a host or receive an existing `ReactorPlatformView`:
149
+
150
+ ```typescript
151
+ import {
152
+ buildPapReactor,
153
+ discoverCompany,
154
+ } from '@datalayer/personal-agent-protocol';
155
+
156
+ const reactor = buildPapReactor({}, [mySecurityPlugin]);
157
+ const company = await discoverCompany('example.com', { reactor });
158
+ ```
159
+
160
+ See the [Reactor repository](https://github.com/datalayer/reactor) for the
161
+ plugin model and contribution lifecycle.
162
+
163
+ ## What is implemented
164
+
165
+ Python and TypeScript share fixtures and cover the same principal protocol
166
+ surface:
167
+
168
+ | Area | Current support |
169
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
170
+ | Models | Forward-compatible discovery, OAuth metadata, client metadata, messages, events, and Operations v1 models |
171
+ | Discovery | `poppy.json`, company-domain and issuer validation, `poppy_domains`, bounded responses, and redirect controls |
172
+ | Sessions | Pairwise user IDs, PAP 4.2 assertions, signed-out start and renewal, redacted tokens, and DPoP nonce retry |
173
+ | Cryptography | ES256/RS256 JOSE, RFC 9449 DPoP creation and verification, freshness, token hash, nonce, and replay checks |
174
+ | Direct Sign-In | State, S256 PKCE, callback validation, browser handoff, and one-use token exchange orchestration |
175
+ | Browser Session | Assertion creation, controlled form POST, replay-safe verification, and company-domain return validation |
176
+ | Transport | Host-owned authorization headers, fresh redirect-bound proofs, one nonce retry, and cross-origin credential isolation |
177
+ | Conversations | Start and continue, random user-global message IDs, ordered long polling, cursor recovery, duplicate suppression, and safe errors |
178
+ | Extensibility | Reactor contribution points for platform, security, protocol, storage, and CLI behavior |
179
+ | CLI | Non-secret profiles, company verification, extension diagnostics, and versioned JSON output |
180
+
181
+ Python additionally provides a SQLite pairwise-identity reference provider.
182
+ The portable TypeScript entry rejects obvious local and private literal
183
+ addresses. Its explicit server entry adds `NodeUrlSafetyPolicy`, `JoseSigner`,
184
+ and DPoP verification with A/AAAA resolution checks:
185
+
186
+ ```typescript
187
+ import {
188
+ JoseSigner,
189
+ NodeUrlSafetyPolicy,
190
+ } from '@datalayer/personal-agent-protocol/server';
191
+ ```
192
+
193
+ Duplicate JSON members are rejected at Python and TypeScript trust boundaries.
194
+
195
+ ## What is not implemented yet
196
+
197
+ The following work remains before the SDKs cover the complete planned PAP
198
+ surface:
199
+
200
+ - managed key custody and durable replay storage;
201
+ - Account Token exchange;
202
+ - resource-bound MCP Bearer transport;
203
+ - device and mediated sign-in;
204
+ - conversation SSE streaming, handoff, and explicit close requests;
205
+ - the Operations client and server;
206
+ - transport-level address pinning to close the DNS-rebinding interval.
79
207
 
80
- The core also contributes UTC time and operating-system randomness. It does
81
- not provide insecure fallbacks for signing, DPoP proof generation, protected
82
- keys, token storage, cache, or browser handoff. A flow requiring one fails
83
- closed until its host contributes it. This also applies to pairwise identity;
84
- Python includes an explicit SQLite reference provider, but the core does not
85
- silently install it.
208
+ Models for Operations v1 exist, but a complete Operations workflow does not.
209
+ Applications should not infer client support from model availability alone.
86
210
 
87
- ### Command line
211
+ ## Security boundary
88
212
 
89
- The Python package installs an extensible `pap` command. Its command groups are
90
- provided through Reactor's `datalayer.reactor.cli` entry-point contract.
213
+ PAP credentials belong to the host, not to an agent model or tool call.
214
+
215
+ - Session Tokens, Account Tokens, proofs, nonces, authorization codes, and
216
+ pairwise identifiers must not enter prompts or model-visible tool results.
217
+ - Direct Sign-In requires host-provided state storage, browser handoff,
218
+ signing, and DPoP contributions.
219
+ - Session requests fail closed until suitable signer and DPoP providers are
220
+ installed.
221
+ - Redirects receive fresh, target-bound proofs; caller-supplied
222
+ `Authorization` and `DPoP` headers are rejected.
223
+ - CLI profiles store configuration only—never tokens or private keys.
224
+
225
+ The guides explain these boundaries at each flow rather than treating them as
226
+ application conventions.
227
+
228
+ ## Command line
229
+
230
+ The Python distribution installs an extensible `pap` command. Its command
231
+ groups use Reactor's `datalayer.reactor.cli` entry-point contract.
91
232
 
92
233
  ```bash
93
234
  pap config set domain example.com --profile work
@@ -96,54 +237,67 @@ pap company verify --profile work --json
96
237
  pap extensions list --profile work
97
238
  ```
98
239
 
99
- Only non-secret settings belong in CLI profiles. See the
100
- [CLI guide](https://personalagentprotocol.org/docs/guides/cli) for precedence,
101
- security boundaries, machine output, and third-party extension examples.
240
+ See the [CLI guide](https://personalagentprotocol.org/docs/guides/cli) for
241
+ configuration precedence, security boundaries, machine-readable output, and
242
+ third-party extensions.
102
243
 
103
- ## TypeScript
244
+ ## Examples and first consumer
104
245
 
105
- The TypeScript package lives at the repository root: sources are in
106
- [`src/`](./src), tests in [`test/`](./test), and emitted package files in
107
- `lib/`.
246
+ The [`examples/pydantic-ai`](./examples/pydantic-ai/) directory contains small,
247
+ focused Python examples. Each example teaches one feature—discovery, identity,
248
+ Sessions, Direct Sign-In, Browser Session, cryptography, conversations, or
249
+ proof-bound transport—without giving protocol secrets to the model.
108
250
 
109
- ```bash
110
- npm install
111
- npm test
112
- ```
251
+ [Datalayer Agent Runtimes](https://github.com/datalayer/agent-runtimes) is the
252
+ first consumer of these SDKs. Its Personal Agent Protocol gallery turns the
253
+ same features into interactive Reactor/Loop applications, including a guided
254
+ person-to-company journey rendered with the runtime's real message UI. The
255
+ gallery's local company simulators are deterministic and clearly identified;
256
+ the PAP clients, transports, validation, and parsed exchanges are real.
113
257
 
114
- ```typescript
115
- import { discoverCompany } from "@datalayer/personal-agent-protocol";
258
+ Each implemented feature should have all three learning layers:
116
259
 
117
- const company = await discoverCompany("example.com");
118
- console.log(company.document.organization.name);
119
- ```
260
+ 1. a focused SDK example in this repository;
261
+ 1. a Docusaurus guide with an explanation and Mermaid sequence diagram;
262
+ 1. an interactive Agent Runtimes example when a UI materially clarifies the
263
+ flow.
120
264
 
121
- Use `buildPapReactor(options, plugins)` to assemble the TypeScript host, or
122
- pass an existing `ReactorPlatformView` as `discoverCompany(..., { reactor })`.
123
- The package bundles Reactor's framework-neutral core in its ESM entry so it can
124
- run directly in Node as well as through a browser bundler.
265
+ ## Develop the SDKs
125
266
 
126
- Server-side TypeScript callers can import `NodeUrlSafetyPolicy`, `JoseSigner`,
127
- and DPoP verification from the explicit
128
- `@datalayer/personal-agent-protocol/server` entry. The portable entry excludes
129
- those server APIs and rejects obvious local/private literal addresses; the Node
130
- policy additionally checks every resolved A and AAAA address.
267
+ Clone the repository, then install the dependencies for the SDK you are
268
+ changing.
131
269
 
132
- The Python and Node preflights reject a host if any resolved address is
133
- non-public. Transport-level address pinning, required to close the
134
- DNS-rebinding interval, is still part of the security work before the first
135
- beta.
270
+ ### Python
136
271
 
137
- ## Examples
272
+ ```bash
273
+ python -m pip install -e '.[test,lint,typing,examples]'
274
+ python -m pytest personal_agent_protocol/__tests__
275
+ ```
138
276
 
139
- The [`examples/`](./examples/) directory includes an Agent Runtimes Python
140
- application that discovers a PAP company over HTTPS while exposing only
141
- verified, non-secret capability metadata to the model. Authorization and token
142
- handling remain outside the agent tool boundary.
277
+ ### TypeScript
143
278
 
144
- ## Protocol sources
279
+ The TypeScript package is at the repository root. Sources and tests live under
280
+ `src/`; published JavaScript and declarations are emitted to `lib/`.
145
281
 
282
+ ```bash
283
+ npm install
284
+ npm test
285
+ ```
286
+
287
+ The shared fixtures are in `fixtures/`. Documentation is a Docusaurus site in
288
+ `docs/`.
289
+
290
+ ## Learn more
291
+
292
+ - [Documentation](https://personalagentprotocol.org/docs)
146
293
  - [Overview](https://personalagentprotocol.org/docs/overview)
147
294
  - [Specification](https://personalagentprotocol.org/docs/spec)
148
295
  - [Implementation guides](https://personalagentprotocol.org/docs/guides)
149
- - [Operations extension](https://personalagentprotocol.org/docs/extensions/operations)
296
+ - [Extensions](https://personalagentprotocol.org/docs/extensions)
297
+ - [Open topics](https://personalagentprotocol.org/docs/open-topics)
298
+ - [Datalayer Reactor](https://github.com/datalayer/reactor)
299
+ - [Datalayer Agent Runtimes](https://github.com/datalayer/agent-runtimes)
300
+
301
+ ## License
302
+
303
+ BSD 3-Clause. See [`LICENSE`](./LICENSE).
@@ -0,0 +1,26 @@
1
+ import { type KeyInput } from "jose";
2
+ import { SecretValue } from "./secret.js";
3
+ export interface BrowserAssertionReplayCache {
4
+ markOnce(key: string, options: {
5
+ readonly expiresAt: Date;
6
+ }): Promise<boolean>;
7
+ }
8
+ export interface VerifyBrowserAssertionOptions {
9
+ readonly publicKey: KeyInput | Uint8Array;
10
+ readonly expectedEndpoint: string;
11
+ readonly companyDomain: string;
12
+ readonly expectedClientId: string;
13
+ readonly expectedPairwiseUserId: string;
14
+ readonly expectedSessionId: string;
15
+ readonly replayCache: BrowserAssertionReplayCache;
16
+ readonly now?: Date;
17
+ }
18
+ export interface VerifiedBrowserAssertion {
19
+ readonly clientId: string;
20
+ readonly pairwiseUserId: string;
21
+ readonly sessionId: string;
22
+ readonly returnTo: string;
23
+ readonly expiresAt: Date;
24
+ }
25
+ export declare function verifyBrowserAssertion(assertion: SecretValue | string, options: VerifyBrowserAssertionOptions): Promise<VerifiedBrowserAssertion>;
26
+ //# sourceMappingURL=browser-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-server.d.ts","sourceRoot":"","sources":["../src/browser-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AAUvE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,2BAA2B,CAAC;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,WAAW,GAAG,MAAM,EAC/B,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,wBAAwB,CAAC,CAqDnC"}
@@ -0,0 +1,61 @@
1
+ import { decodeProtectedHeader, jwtVerify } from "jose";
2
+ import { BROWSER_ASSERTION_TYPE, assertCompanyReturnTo, browserAssertionError, validateBrowserClaimIdentity, } from "./browser.js";
3
+ import { BrowserSessionError } from "./errors.js";
4
+ import { SecretValue } from "./secret.js";
5
+ export async function verifyBrowserAssertion(assertion, options) {
6
+ const compact = assertion instanceof SecretValue ? assertion.reveal() : assertion;
7
+ let claims;
8
+ try {
9
+ const header = decodeProtectedHeader(compact);
10
+ if (header.typ !== BROWSER_ASSERTION_TYPE || (header.alg !== "ES256" && header.alg !== "RS256")) {
11
+ throw browserAssertionError();
12
+ }
13
+ const verified = await jwtVerify(compact, options.publicKey, {
14
+ algorithms: [header.alg],
15
+ typ: BROWSER_ASSERTION_TYPE,
16
+ requiredClaims: ["iss", "sub", "aud", "session_id", "return_to", "iat", "exp", "jti"],
17
+ ...(options.now === undefined ? {} : { currentDate: options.now }),
18
+ });
19
+ const payload = verified.payload;
20
+ claims = {
21
+ iss: validateBrowserClaimIdentity(payload.iss, "iss"),
22
+ sub: validateBrowserClaimIdentity(payload.sub, "sub"),
23
+ aud: validateBrowserClaimIdentity(payload.aud, "aud"),
24
+ session_id: validateBrowserClaimIdentity(payload.session_id, "session_id"),
25
+ return_to: validateBrowserClaimIdentity(payload.return_to, "return_to"),
26
+ iat: Number(payload.iat),
27
+ exp: Number(payload.exp),
28
+ jti: validateBrowserClaimIdentity(payload.jti, "jti"),
29
+ };
30
+ }
31
+ catch (cause) {
32
+ if (cause instanceof BrowserSessionError)
33
+ throw cause;
34
+ throw browserAssertionError(cause);
35
+ }
36
+ const now = options.now ?? new Date();
37
+ const nowSeconds = Math.floor(now.getTime() / 1000);
38
+ if (!Number.isInteger(claims.iat) || !Number.isInteger(claims.exp) ||
39
+ claims.exp <= claims.iat || claims.exp - claims.iat > 60 ||
40
+ claims.iat > nowSeconds || claims.exp <= nowSeconds) {
41
+ throw browserAssertionError();
42
+ }
43
+ const endpoint = new URL(options.expectedEndpoint).toString();
44
+ const returnTo = assertCompanyReturnTo(claims.return_to, options.companyDomain);
45
+ if (claims.aud !== endpoint || claims.iss !== options.expectedClientId ||
46
+ claims.sub !== options.expectedPairwiseUserId || claims.session_id !== options.expectedSessionId) {
47
+ throw browserAssertionError();
48
+ }
49
+ const expiresAt = new Date(claims.exp * 1000);
50
+ if (!(await options.replayCache.markOnce(`browser:${claims.iss}:${claims.jti}`, { expiresAt }))) {
51
+ throw new BrowserSessionError("invalid_browser_assertion", "Browser assertion has already been used");
52
+ }
53
+ return {
54
+ clientId: claims.iss,
55
+ pairwiseUserId: claims.sub,
56
+ sessionId: claims.session_id,
57
+ returnTo,
58
+ expiresAt,
59
+ };
60
+ }
61
+ //# sourceMappingURL=browser-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-server.js","sourceRoot":"","sources":["../src/browser-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { ReactorPlatformView } from "@datalayer/reactor";
2
+ import { BrowserSessionError } from "./errors.js";
3
+ import { SecretValue } from "./secret.js";
4
+ export declare const BROWSER_ASSERTION_TYPE = "poppy-browser+jwt";
5
+ export interface BrowserAssertionClaims {
6
+ readonly iss: string;
7
+ readonly sub: string;
8
+ readonly aud: string;
9
+ readonly session_id: string;
10
+ readonly return_to: string;
11
+ readonly iat: number;
12
+ readonly exp: number;
13
+ readonly jti: string;
14
+ }
15
+ export interface BrowserAssertionOptions {
16
+ readonly clientId: string;
17
+ readonly pairwiseUserId: string;
18
+ readonly browserSessionEndpoint: string;
19
+ readonly sessionId: string;
20
+ readonly returnTo: string;
21
+ readonly companyDomain: string;
22
+ readonly now?: Date;
23
+ readonly lifetimeSeconds?: number;
24
+ readonly randomBytes?: (length: number) => Uint8Array;
25
+ }
26
+ export declare function buildBrowserAssertionClaims(options: BrowserAssertionOptions): BrowserAssertionClaims;
27
+ export interface BrowserSessionStart {
28
+ readonly endpoint: string;
29
+ readonly returnTo: string;
30
+ readonly expiresAt: Date;
31
+ }
32
+ export declare class BrowserSessionClient {
33
+ #private;
34
+ constructor(options: {
35
+ readonly reactor: ReactorPlatformView;
36
+ });
37
+ join(options: Omit<BrowserAssertionOptions, "now" | "randomBytes">): Promise<BrowserSessionStart>;
38
+ }
39
+ export declare function assertCompanyReturnTo(value: string, companyDomain: string): string;
40
+ export declare function browserAssertionError(cause?: unknown): BrowserSessionError;
41
+ export declare function validateBrowserClaimIdentity(value: unknown, label: string): string;
42
+ export declare function redactBrowserAssertion(value: string): SecretValue;
43
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAE1D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,CAAC;CACvD;AAuCD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CA6BpG;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED,qBAAa,oBAAoB;;gBAGnB,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE;IAIxD,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,KAAK,GAAG,aAAa,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAkBxG;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAElF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAM1E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAEjE"}
package/lib/browser.js ADDED
@@ -0,0 +1,103 @@
1
+ import { BrowserSessionError } from "./errors.js";
2
+ import { BrowserFormPosts, Clocks, RandomSources, Signers, firstContribution } from "./reactor.js";
3
+ import { SecretValue } from "./secret.js";
4
+ export const BROWSER_ASSERTION_TYPE = "poppy-browser+jwt";
5
+ function httpsUrl(value, label) {
6
+ let url;
7
+ try {
8
+ url = new URL(value);
9
+ }
10
+ catch (cause) {
11
+ throw new BrowserSessionError("invalid_url", `${label} must be an HTTPS URL`, { cause });
12
+ }
13
+ if (url.protocol !== "https:" || url.username || url.password) {
14
+ throw new BrowserSessionError("invalid_url", `${label} must be an HTTPS URL`);
15
+ }
16
+ return url;
17
+ }
18
+ function companyReturnTo(value, companyDomain) {
19
+ const url = httpsUrl(value, "returnTo");
20
+ const host = url.hostname.toLowerCase().replace(/\.$/u, "");
21
+ const domain = companyDomain.toLowerCase().replace(/\.$/u, "");
22
+ if (!domain || (host !== domain && !host.endsWith(`.${domain}`))) {
23
+ throw new BrowserSessionError("invalid_return_to", "returnTo must be on the company domain or a subdomain");
24
+ }
25
+ return url.toString();
26
+ }
27
+ function defaultRandomBytes(length) {
28
+ if (!globalThis.crypto)
29
+ throw new BrowserSessionError("crypto_unavailable", "Web Crypto is required");
30
+ return globalThis.crypto.getRandomValues(new Uint8Array(length));
31
+ }
32
+ function base64Url(bytes) {
33
+ let binary = "";
34
+ for (const value of bytes)
35
+ binary += String.fromCharCode(value);
36
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
37
+ }
38
+ export function buildBrowserAssertionClaims(options) {
39
+ const lifetime = options.lifetimeSeconds ?? 60;
40
+ if (!Number.isInteger(lifetime) || lifetime <= 0 || lifetime > 60) {
41
+ throw new BrowserSessionError("invalid_lifetime", "Browser assertion lifetime must be between 1 and 60 seconds");
42
+ }
43
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(options.pairwiseUserId)) {
44
+ throw new BrowserSessionError("invalid_user_id", "Invalid pairwise user ID");
45
+ }
46
+ if (!/^[A-Za-z0-9_-]{1,256}$/u.test(options.sessionId)) {
47
+ throw new BrowserSessionError("invalid_session_id", "Invalid PAP Session ID");
48
+ }
49
+ const entropy = (options.randomBytes ?? defaultRandomBytes)(16);
50
+ if (entropy.byteLength < 16) {
51
+ throw new BrowserSessionError("weak_randomness", "Browser assertion IDs require 128 random bits");
52
+ }
53
+ const iat = Math.floor((options.now ?? new Date()).getTime() / 1000);
54
+ return {
55
+ iss: httpsUrl(options.clientId, "clientId").toString(),
56
+ sub: options.pairwiseUserId,
57
+ aud: httpsUrl(options.browserSessionEndpoint, "browserSessionEndpoint").toString(),
58
+ session_id: options.sessionId,
59
+ return_to: companyReturnTo(options.returnTo, options.companyDomain),
60
+ iat,
61
+ exp: iat + lifetime,
62
+ jti: `jti_${base64Url(entropy)}`,
63
+ };
64
+ }
65
+ export class BrowserSessionClient {
66
+ #reactor;
67
+ constructor(options) {
68
+ this.#reactor = options.reactor;
69
+ }
70
+ async join(options) {
71
+ const clock = firstContribution(this.#reactor, Clocks, "clock");
72
+ const random = firstContribution(this.#reactor, RandomSources, "random source");
73
+ const signer = firstContribution(this.#reactor, Signers, "signer");
74
+ const browser = firstContribution(this.#reactor, BrowserFormPosts, "browser form POST");
75
+ const claims = buildBrowserAssertionClaims({
76
+ ...options,
77
+ now: clock.now(),
78
+ randomBytes: length => random.bytes(length),
79
+ });
80
+ const assertion = await signer.sign({ ...claims }, { purpose: "browser_assertion" });
81
+ await browser.post(new URL(claims.aud), { assertion });
82
+ return {
83
+ endpoint: claims.aud,
84
+ returnTo: claims.return_to,
85
+ expiresAt: new Date(claims.exp * 1000),
86
+ };
87
+ }
88
+ }
89
+ export function assertCompanyReturnTo(value, companyDomain) {
90
+ return companyReturnTo(value, companyDomain);
91
+ }
92
+ export function browserAssertionError(cause) {
93
+ return new BrowserSessionError("invalid_browser_assertion", "Browser assertion signature or claims are invalid", cause === undefined ? {} : { cause });
94
+ }
95
+ export function validateBrowserClaimIdentity(value, label) {
96
+ if (typeof value !== "string" || !value)
97
+ throw browserAssertionError(new TypeError(`${label} is invalid`));
98
+ return value;
99
+ }
100
+ export function redactBrowserAssertion(value) {
101
+ return new SecretValue(value);
102
+ }
103
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}