@parlor/auth 0.2.0 → 0.3.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DeviceSignature, PresenceSignature } from \"@parlor/capabilities\";\nimport {\n canonicalBytes,\n decodeBase64url,\n encodeBase64url,\n} from \"@parlor/capabilities/encoding\";\nimport type { AnswerToSign, LoggedItem, PromptToSign } from \"@parlor/chat\";\nimport type { DeviceId } from \"@parlor/core\";\nimport type { PresenceKey } from \"@parlor/server\";\n\nimport { derToRaw, verifyEs256 } from \"./es256.js\";\n\nexport interface PromptStatement {\n readonly v: 1;\n readonly kind: \"prompt\";\n /** The app's origin: a statement for one app is worthless to another. */\n readonly app: string;\n readonly threadId: string;\n readonly turnId: string;\n /** `<hostId>/<agentId>`: a prompt cannot be replayed to another agent. */\n readonly agent: string;\n /** base64url SHA-256 of the prompt text */\n readonly digest: string;\n readonly at: number;\n}\n\nexport interface AnswerStatement {\n readonly v: 1;\n readonly kind: \"answer\";\n readonly app: string;\n readonly threadId: string;\n readonly askId: string;\n /** An option id, `\"declined\"`, or the digest of a form's values. */\n readonly choice: string;\n readonly at: number;\n}\n\n/** A linked device's word that the device behind `commitment` is the user's. */\nexport interface VouchStatement {\n readonly v: 1;\n readonly kind: \"vouch\";\n readonly app: string;\n readonly voucher: string;\n readonly device: string;\n /** `deviceCommitment` of the new device's keys, as the voucher checked it. */\n readonly commitment: string;\n readonly at: number;\n}\n\n/** A linked device's word, with presence, that `device` should no longer use the user's agents. */\nexport interface RemoveStatement {\n readonly v: 1;\n readonly kind: \"remove\";\n readonly app: string;\n readonly actor: string;\n readonly device: string;\n readonly at: number;\n}\n\nexport type Statement =\n | PromptStatement\n | AnswerStatement\n | VouchStatement\n | RemoveStatement;\n\nexport interface Signed<S extends Statement> {\n readonly statement: S;\n readonly deviceId: DeviceId;\n readonly signature: S extends AnswerStatement\n ? DeviceSignature | PresenceSignature\n : S extends VouchStatement | RemoveStatement\n ? PresenceSignature\n : DeviceSignature;\n}\n\n/** A device key the verifier pinned itself, never one that travelled with the statement. */\nexport interface PinnedDevice {\n readonly deviceId: DeviceId;\n readonly signingKey: JsonWebKey;\n readonly presenceKey?: PresenceKey | undefined;\n}\n\nexport type VerifyOutcome = \"valid\" | \"invalid\" | \"stale\" | \"presence_required\";\n\nconst FUTURE_SKEW_MS = 5 * 60_000;\nconst USER_PRESENT = 0x01;\nconst USER_VERIFIED = 0x04;\nconst AUTH_DATA_MIN_BYTES = 37;\n\nexport interface VerifyOptions {\n readonly requirePresence: boolean;\n readonly now: number;\n readonly maxAgeMs: number;\n}\n\nconst sha256 = async (bytes: Uint8Array): Promise<Uint8Array> =>\n new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new Uint8Array(bytes)));\n\nconst digestOf = async (bytes: Uint8Array) => encodeBase64url(await sha256(bytes));\n\nexport async function promptStatement(\n app: string,\n prompt: PromptToSign,\n at: number,\n): Promise<PromptStatement> {\n return {\n v: 1,\n kind: \"prompt\",\n app,\n threadId: prompt.threadId,\n turnId: prompt.turnId,\n agent: `${prompt.agent.hostId}/${prompt.agent.agentId}`,\n digest: await digestOf(new TextEncoder().encode(prompt.text)),\n at,\n };\n}\n\nexport async function answerStatement(\n app: string,\n answer: AnswerToSign,\n at: number,\n): Promise<AnswerStatement> {\n return {\n v: 1,\n kind: \"answer\",\n app,\n threadId: answer.threadId,\n askId: answer.askId,\n choice:\n answer.values === undefined\n ? answer.choice\n : await digestOf(\n canonicalBytes({ choice: answer.choice, values: answer.values }),\n ),\n at,\n };\n}\n\nexport async function signedPrompt(\n app: string,\n item: LoggedItem<\"prompt\">,\n): Promise<Signed<PromptStatement> | undefined> {\n const { signed, agent, text } = item.body;\n if (!signed) return undefined;\n return {\n statement: await promptStatement(\n app,\n { threadId: item.threadId, turnId: item.turnId, agent, text },\n signed.at,\n ),\n deviceId: signed.deviceId,\n signature: signed.signature,\n };\n}\n\nexport function statementBytes(statement: Statement): Uint8Array {\n return canonicalBytes(statement);\n}\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nexport const LINK_CODE_LENGTH = 10;\n\n/**\n * What a new device shows to be linked: a hash of its keys, so whoever links it checks the\n * keys the backend hands over are the ones this device holds.\n */\nexport async function deviceCommitment(\n app: string,\n deviceId: string,\n signingKey: JsonWebKey,\n presenceKey: JsonWebKey | undefined,\n): Promise<string> {\n const point = (key: JsonWebKey) => ({ crv: key.crv, kty: key.kty, x: key.x, y: key.y });\n return digestOf(\n canonicalBytes({\n v: 1,\n kind: \"device\",\n app: new URL(app).origin,\n deviceId,\n signing: point(signingKey),\n presence: presenceKey ? point(presenceKey) : null,\n }),\n );\n}\n\n/** The commitment's first 50 bits in Crockford base32, short enough to type. */\nexport function linkCode(commitment: string): string {\n const bytes = decodeBase64url(commitment);\n let bits = 0n;\n for (let index = 0; index < 7; index += 1) bits = (bits << 8n) | BigInt(bytes[index]!);\n bits >>= 6n;\n let code = \"\";\n for (let index = 0; index < LINK_CODE_LENGTH; index += 1) {\n code = CROCKFORD[Number(bits & 31n)]! + code;\n bits >>= 5n;\n }\n return code;\n}\n\n/** Upper case, no separators, the letters Crockford reads as digits mapped back. */\nexport function normalizeLinkCode(typed: string): string {\n return typed\n .toUpperCase()\n .replace(/[\\s-]/g, \"\")\n .replace(/O/g, \"0\")\n .replace(/[IL]/g, \"1\");\n}\n\nexport function formatLinkCode(code: string): string {\n return `${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8)}`;\n}\n\nexport function vouchStatement(\n app: string,\n voucher: string,\n device: string,\n commitment: string,\n at: number,\n): VouchStatement {\n return {\n v: 1,\n kind: \"vouch\",\n app: new URL(app).origin,\n voucher,\n device,\n commitment,\n at,\n };\n}\n\nexport function removeStatement(\n app: string,\n actor: string,\n device: string,\n at: number,\n): RemoveStatement {\n return { v: 1, kind: \"remove\", app: new URL(app).origin, actor, device, at };\n}\n\n/** The fragment Parlor Host's loopback redirect carries back to the browser that approved `connect`. */\nexport const HOST_PAIRING_FRAGMENT = \"parlor-host-pairing\";\n\nconst pairingKey = (secret: string) =>\n crypto.subtle.importKey(\n \"raw\",\n new Uint8Array(decodeBase64url(secret)),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n\nconst pairingBytes = (deviceId: string, signingKey: JsonWebKey) =>\n canonicalBytes({\n v: 1,\n kind: \"pairing\",\n deviceId,\n signingKey: {\n crv: signingKey.crv,\n kty: signingKey.kty,\n x: signingKey.x,\n y: signingKey.y,\n },\n });\n\n/**\n * The browser that approved `connect` gets the host's pairing secret through the loopback\n * redirect, never through the app; this binds it to one device's keys.\n */\nexport async function pairingProof(\n secret: string,\n deviceId: string,\n signingKey: JsonWebKey,\n): Promise<string> {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await pairingKey(secret),\n new Uint8Array(pairingBytes(deviceId, signingKey)),\n );\n return encodeBase64url(new Uint8Array(mac));\n}\n\nexport async function checkPairingProof(\n secret: string,\n proof: string,\n deviceId: string,\n signingKey: JsonWebKey,\n): Promise<boolean> {\n try {\n return await crypto.subtle.verify(\n \"HMAC\",\n await pairingKey(secret),\n new Uint8Array(decodeBase64url(proof)),\n new Uint8Array(pairingBytes(deviceId, signingKey)),\n );\n } catch {\n return false;\n }\n}\n\ninterface ClientData {\n readonly type?: unknown;\n readonly challenge?: unknown;\n readonly origin?: unknown;\n readonly crossOrigin?: unknown;\n}\n\nasync function assertionHolds(\n statement: Statement,\n signature: DeviceSignature,\n presenceKey: PinnedDevice[\"presenceKey\"],\n): Promise<boolean> {\n if (presenceKey?.format !== \"webauthn\" || signature.presence !== \"user\") return false;\n if (\n signature.authenticatorData === undefined ||\n signature.clientDataJSON === undefined\n ) {\n return false;\n }\n if (signature.keyId !== presenceKey.credentialId) return false;\n if (new URL(statement.app).hostname !== presenceKey.rpId) return false;\n const clientDataBytes = decodeBase64url(signature.clientDataJSON);\n const clientData = JSON.parse(new TextDecoder().decode(clientDataBytes)) as ClientData;\n const challenge = await digestOf(statementBytes(statement));\n if (\n clientData.type !== \"webauthn.get\" ||\n clientData.challenge !== challenge ||\n clientData.origin !== statement.app ||\n clientData.crossOrigin === true\n ) {\n return false;\n }\n const authData = decodeBase64url(signature.authenticatorData);\n const flags = authData[32];\n if (authData.length < AUTH_DATA_MIN_BYTES || flags === undefined) return false;\n if ((flags & USER_PRESENT) === 0 || (flags & USER_VERIFIED) === 0) return false;\n const rpIdHash = await sha256(new TextEncoder().encode(presenceKey.rpId));\n if (encodeBase64url(authData.slice(0, 32)) !== encodeBase64url(rpIdHash)) return false;\n const raw = derToRaw(decodeBase64url(signature.signature));\n if (!raw) return false;\n const signed = new Uint8Array([...authData, ...(await sha256(clientDataBytes))]);\n return verifyEs256(presenceKey.publicKey, raw, signed);\n}\n\nasync function signatureHolds(\n statement: Statement,\n signature: DeviceSignature,\n pinned: PinnedDevice,\n): Promise<boolean> {\n if (signature.format === \"webauthn\") {\n return assertionHolds(statement, signature, pinned.presenceKey);\n }\n const bytes = statementBytes(statement);\n const raw = decodeBase64url(signature.signature);\n if (signature.presence === \"none\") return verifyEs256(pinned.signingKey, raw, bytes);\n const { presenceKey } = pinned;\n return presenceKey?.format === \"es256-raw\"\n ? verifyEs256(presenceKey.publicKey, raw, bytes)\n : false;\n}\n\nexport async function verifyStatement(\n signed: Signed<Statement>,\n pinned: PinnedDevice,\n options: VerifyOptions,\n): Promise<VerifyOutcome> {\n const { statement, signature } = signed;\n if (signed.deviceId !== pinned.deviceId) return \"invalid\";\n if (options.now - statement.at > options.maxAgeMs) return \"stale\";\n if (statement.at - options.now > FUTURE_SKEW_MS) return \"invalid\";\n if (options.requirePresence && signature.presence !== \"user\") {\n return \"presence_required\";\n }\n try {\n return (await signatureHolds(statement, signature, pinned)) ? \"valid\" : \"invalid\";\n } catch {\n return \"invalid\";\n }\n}\n"]}
1
+ {"version":3,"file":"statements.js","sourceRoot":"","sources":["../src/statements.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DeviceSignature, PresenceSignature } from \"@parlor/capabilities\";\nimport {\n canonicalBytes,\n decodeBase64url,\n encodeBase64url,\n} from \"@parlor/capabilities/encoding\";\nimport type { AnswerToSign, LoggedItem, PromptToSign } from \"@parlor/chat\";\nimport type { DeviceId } from \"@parlor/core\";\nimport type { PresenceKey } from \"@parlor/server\";\n\nimport { derToRaw, verifyEs256 } from \"./es256.js\";\n\nexport interface PromptStatement {\n readonly v: 1;\n readonly kind: \"prompt\";\n /** The app's origin: a statement for one app is worthless to another. */\n readonly app: string;\n readonly threadId: string;\n readonly turnId: string;\n /** `<hostId>/<agentId>`: a prompt cannot be replayed to another agent. */\n readonly agent: string;\n /** base64url SHA-256 of the prompt text */\n readonly digest: string;\n readonly at: number;\n}\n\nexport interface AnswerStatement {\n readonly v: 1;\n readonly kind: \"answer\";\n readonly app: string;\n readonly threadId: string;\n readonly askId: string;\n /** An option id, `\"declined\"`, or the digest of a form's values. */\n readonly choice: string;\n readonly at: number;\n}\n\n/** A linked device's word that the device behind `commitment` is the user's. */\nexport interface VouchStatement {\n readonly v: 1;\n readonly kind: \"vouch\";\n readonly app: string;\n readonly voucher: string;\n readonly device: string;\n /** `deviceCommitment` of the new device's keys, as the voucher checked it. */\n readonly commitment: string;\n readonly at: number;\n}\n\n/** A linked device's word, with presence, that `device` should no longer use the user's agents. */\nexport interface RemoveStatement {\n readonly v: 1;\n readonly kind: \"remove\";\n readonly app: string;\n readonly actor: string;\n readonly device: string;\n readonly at: number;\n}\n\nexport type Statement =\n | PromptStatement\n | AnswerStatement\n | VouchStatement\n | RemoveStatement;\n\nexport interface Signed<S extends Statement> {\n readonly statement: S;\n readonly deviceId: DeviceId;\n readonly signature: S extends AnswerStatement\n ? DeviceSignature | PresenceSignature\n : S extends VouchStatement | RemoveStatement\n ? PresenceSignature\n : DeviceSignature;\n}\n\n/** A device key the verifier pinned itself, never one that travelled with the statement. */\nexport interface PinnedDevice {\n readonly deviceId: DeviceId;\n readonly signingKey: JsonWebKey;\n readonly presenceKey?: PresenceKey | undefined;\n}\n\nexport type VerifyOutcome = \"valid\" | \"invalid\" | \"stale\" | \"presence_required\";\n\nconst FUTURE_SKEW_MS = 5 * 60_000;\nconst USER_PRESENT = 0x01;\nconst USER_VERIFIED = 0x04;\nconst AUTH_DATA_MIN_BYTES = 37;\n\nexport interface VerifyOptions {\n readonly requirePresence: boolean;\n readonly now: number;\n readonly maxAgeMs: number;\n}\n\nconst sha256 = async (bytes: Uint8Array): Promise<Uint8Array> =>\n new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new Uint8Array(bytes)));\n\nconst digestOf = async (bytes: Uint8Array) => encodeBase64url(await sha256(bytes));\n\nexport async function promptStatement(\n app: string,\n prompt: PromptToSign,\n at: number,\n): Promise<PromptStatement> {\n return {\n v: 1,\n kind: \"prompt\",\n app,\n threadId: prompt.threadId,\n turnId: prompt.turnId,\n agent: `${prompt.agent.hostId}/${prompt.agent.agentId}`,\n digest: await digestOf(new TextEncoder().encode(prompt.text)),\n at,\n };\n}\n\nexport async function answerStatement(\n app: string,\n answer: AnswerToSign,\n at: number,\n): Promise<AnswerStatement> {\n return {\n v: 1,\n kind: \"answer\",\n app,\n threadId: answer.threadId,\n askId: answer.askId,\n choice:\n answer.values === undefined\n ? answer.choice\n : await digestOf(\n canonicalBytes({ choice: answer.choice, values: answer.values }),\n ),\n at,\n };\n}\n\nexport async function signedPrompt(\n app: string,\n item: LoggedItem<\"prompt\">,\n): Promise<Signed<PromptStatement> | undefined> {\n const { signed, agent, text } = item.body;\n if (!signed) return undefined;\n return {\n statement: await promptStatement(\n app,\n { threadId: item.threadId, turnId: item.turnId, agent, text },\n signed.at,\n ),\n deviceId: signed.deviceId,\n signature: signed.signature,\n };\n}\n\nexport function statementBytes(statement: Statement): Uint8Array {\n return canonicalBytes(statement);\n}\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nexport const LINK_CODE_LENGTH = 10;\n\n/**\n * What a new device shows to be linked: a hash of its keys, so whoever links it checks the\n * keys the backend hands over are the ones this device holds.\n */\nexport async function deviceCommitment(\n app: string,\n deviceId: string,\n signingKey: JsonWebKey,\n presenceKey: JsonWebKey | undefined,\n): Promise<string> {\n const point = (key: JsonWebKey) => ({ crv: key.crv, kty: key.kty, x: key.x, y: key.y });\n return digestOf(\n canonicalBytes({\n v: 1,\n kind: \"device\",\n app: new URL(app).origin,\n deviceId,\n signing: point(signingKey),\n presence: presenceKey ? point(presenceKey) : null,\n }),\n );\n}\n\n/** The commitment's first 50 bits in Crockford base32, short enough to type. */\nexport function linkCode(commitment: string): string {\n const bytes = decodeBase64url(commitment);\n let bits = 0n;\n for (let index = 0; index < 7; index += 1) bits = (bits << 8n) | BigInt(bytes[index]!);\n bits >>= 6n;\n let code = \"\";\n for (let index = 0; index < LINK_CODE_LENGTH; index += 1) {\n code = CROCKFORD[Number(bits & 31n)]! + code;\n bits >>= 5n;\n }\n return code;\n}\n\n/** Upper case, no separators, the letters Crockford reads as digits mapped back. */\nexport function normalizeLinkCode(typed: string): string {\n return typed\n .toUpperCase()\n .replace(/[\\s-]/g, \"\")\n .replace(/O/g, \"0\")\n .replace(/[IL]/g, \"1\");\n}\n\nexport function formatLinkCode(code: string): string {\n return `${code.slice(0, 4)}-${code.slice(4, 8)}-${code.slice(8)}`;\n}\n\nexport function vouchStatement(\n app: string,\n voucher: string,\n device: string,\n commitment: string,\n at: number,\n): VouchStatement {\n return {\n v: 1,\n kind: \"vouch\",\n app: new URL(app).origin,\n voucher,\n device,\n commitment,\n at,\n };\n}\n\nexport function removeStatement(\n app: string,\n actor: string,\n device: string,\n at: number,\n): RemoveStatement {\n return { v: 1, kind: \"remove\", app: new URL(app).origin, actor, device, at };\n}\n\n/** The fragment Parlor Host's loopback redirect carries back to the browser that approved `connect`. */\nexport const HOST_PAIRING_FRAGMENT = \"parlor-host-pairing\";\n\n/** Where a browser keeps that secret, for its first prompt to prove. */\nexport const HOST_PAIRING_KEY = \"parlor:host-pairing\";\n\nconst pairingKey = (secret: string) =>\n crypto.subtle.importKey(\n \"raw\",\n new Uint8Array(decodeBase64url(secret)),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n\nconst pairingBytes = (deviceId: string, signingKey: JsonWebKey) =>\n canonicalBytes({\n v: 1,\n kind: \"pairing\",\n deviceId,\n signingKey: {\n crv: signingKey.crv,\n kty: signingKey.kty,\n x: signingKey.x,\n y: signingKey.y,\n },\n });\n\n/**\n * The browser that approved `connect` gets the host's pairing secret through the loopback\n * redirect, never through the app; this binds it to one device's keys.\n */\nexport async function pairingProof(\n secret: string,\n deviceId: string,\n signingKey: JsonWebKey,\n): Promise<string> {\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n await pairingKey(secret),\n new Uint8Array(pairingBytes(deviceId, signingKey)),\n );\n return encodeBase64url(new Uint8Array(mac));\n}\n\nexport async function checkPairingProof(\n secret: string,\n proof: string,\n deviceId: string,\n signingKey: JsonWebKey,\n): Promise<boolean> {\n try {\n return await crypto.subtle.verify(\n \"HMAC\",\n await pairingKey(secret),\n new Uint8Array(decodeBase64url(proof)),\n new Uint8Array(pairingBytes(deviceId, signingKey)),\n );\n } catch {\n return false;\n }\n}\n\ninterface ClientData {\n readonly type?: unknown;\n readonly challenge?: unknown;\n readonly origin?: unknown;\n readonly crossOrigin?: unknown;\n}\n\nasync function assertionHolds(\n statement: Statement,\n signature: DeviceSignature,\n presenceKey: PinnedDevice[\"presenceKey\"],\n): Promise<boolean> {\n if (presenceKey?.format !== \"webauthn\" || signature.presence !== \"user\") return false;\n if (\n signature.authenticatorData === undefined ||\n signature.clientDataJSON === undefined\n ) {\n return false;\n }\n if (signature.keyId !== presenceKey.credentialId) return false;\n if (new URL(statement.app).hostname !== presenceKey.rpId) return false;\n const clientDataBytes = decodeBase64url(signature.clientDataJSON);\n const clientData = JSON.parse(new TextDecoder().decode(clientDataBytes)) as ClientData;\n const challenge = await digestOf(statementBytes(statement));\n if (\n clientData.type !== \"webauthn.get\" ||\n clientData.challenge !== challenge ||\n clientData.origin !== statement.app ||\n clientData.crossOrigin === true\n ) {\n return false;\n }\n const authData = decodeBase64url(signature.authenticatorData);\n const flags = authData[32];\n if (authData.length < AUTH_DATA_MIN_BYTES || flags === undefined) return false;\n if ((flags & USER_PRESENT) === 0 || (flags & USER_VERIFIED) === 0) return false;\n const rpIdHash = await sha256(new TextEncoder().encode(presenceKey.rpId));\n if (encodeBase64url(authData.slice(0, 32)) !== encodeBase64url(rpIdHash)) return false;\n const raw = derToRaw(decodeBase64url(signature.signature));\n if (!raw) return false;\n const signed = new Uint8Array([...authData, ...(await sha256(clientDataBytes))]);\n return verifyEs256(presenceKey.publicKey, raw, signed);\n}\n\nasync function signatureHolds(\n statement: Statement,\n signature: DeviceSignature,\n pinned: PinnedDevice,\n): Promise<boolean> {\n if (signature.format === \"webauthn\") {\n return assertionHolds(statement, signature, pinned.presenceKey);\n }\n const bytes = statementBytes(statement);\n const raw = decodeBase64url(signature.signature);\n if (signature.presence === \"none\") return verifyEs256(pinned.signingKey, raw, bytes);\n const { presenceKey } = pinned;\n return presenceKey?.format === \"es256-raw\"\n ? verifyEs256(presenceKey.publicKey, raw, bytes)\n : false;\n}\n\nexport async function verifyStatement(\n signed: Signed<Statement>,\n pinned: PinnedDevice,\n options: VerifyOptions,\n): Promise<VerifyOutcome> {\n const { statement, signature } = signed;\n if (signed.deviceId !== pinned.deviceId) return \"invalid\";\n if (options.now - statement.at > options.maxAgeMs) return \"stale\";\n if (statement.at - options.now > FUTURE_SKEW_MS) return \"invalid\";\n if (options.requirePresence && signature.presence !== \"user\") {\n return \"presence_required\";\n }\n try {\n return (await signatureHolds(statement, signature, pinned)) ? \"valid\" : \"invalid\";\n } catch {\n return \"invalid\";\n }\n}\n"]}
package/dist/tokens.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { type AgentPrincipal, type HostPrincipal, type TurnBinding } from "@parlor/core";
2
- import { type Delegation, type TurnDelegation } from "@parlor/server";
2
+ import { type AgentDelegation, type Delegation, type TurnDelegation } from "@parlor/server";
3
3
  /** A host assertion's audience: signed by the computer's own key, per request. */
4
4
  export declare const HOST_AUDIENCE = "parlor:host";
5
5
  /** A turn token's audience: signed by the server, carried by the host's MCP proxy. */
6
6
  export declare const TURN_AUDIENCE = "parlor:mcp";
7
7
  export declare const HOST_ASSERTION_TYP = "parlor-host+jwt";
8
8
  export declare const TURN_TOKEN_TYP = "parlor-turn+jwt";
9
+ /** An agent on the host's computer outside any turn: Claude Code or Codex in a terminal. */
10
+ export declare const AGENT_TOKEN_TYP = "parlor-agent+jwt";
9
11
  /**
10
12
  * A turn token never outlives the revocation horizon, or a revoked grant's token could
11
13
  * verify after `RevocationSet` forgot the revocation.
@@ -19,6 +21,11 @@ export type Jws<A extends TokenAudience> = string & {
19
21
  };
20
22
  export type HostAssertion = Jws<typeof HOST_AUDIENCE>;
21
23
  export type TurnToken = Jws<typeof TURN_AUDIENCE>;
24
+ declare const outsideTurns: unique symbol;
25
+ /** Same audience as a turn token, never mistaken for one: no turn, so no screen to drive. */
26
+ export type AgentToken = Jws<typeof TURN_AUDIENCE> & {
27
+ readonly [outsideTurns]: true;
28
+ };
22
29
  /** What an `authorization` header carries, sorted by scheme and shape before any verification. */
23
30
  export type Credential = {
24
31
  readonly kind: "host";
@@ -26,6 +33,9 @@ export type Credential = {
26
33
  } | {
27
34
  readonly kind: "turn";
28
35
  readonly token: TurnToken;
36
+ } | {
37
+ readonly kind: "agent";
38
+ readonly token: AgentToken;
29
39
  } | {
30
40
  readonly kind: "access";
31
41
  readonly token: string;
@@ -65,5 +75,21 @@ export declare function verifyTurnToken(keys: TurnTokenKeys, token: TurnToken, n
65
75
  export declare function agentPrincipalOf(claims: TurnTokenClaims): AgentPrincipal & {
66
76
  readonly turn: TurnBinding;
67
77
  };
78
+ export interface AgentTokenClaims {
79
+ readonly aud: typeof TURN_AUDIENCE;
80
+ readonly iss: string;
81
+ readonly sub: string;
82
+ readonly gid: string;
83
+ readonly hid: string;
84
+ readonly agt: string;
85
+ readonly iat: number;
86
+ readonly exp: number;
87
+ }
88
+ /** Mints a token for an agent on `host`'s computer outside any turn; the provider's `delegateAgent`. */
89
+ export declare function delegateAgent(keys: TurnTokenKeys, host: HostPrincipal, agent: AgentDelegation, now: number): Promise<Delegation>;
90
+ /** Signature, `aud`, `iss` and `exp` only; the caller then asks `RevocationSet` about `gid`. */
91
+ export declare function verifyAgentToken(keys: TurnTokenKeys, token: AgentToken, now: number): Promise<AgentTokenClaims>;
92
+ /** An external agent of the user: no turn, so `ui_*` reaches no screen and confirms go to the app. */
93
+ export declare function outsideAgentPrincipalOf(claims: AgentTokenClaims): AgentPrincipal;
68
94
  export {};
69
95
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
package/dist/tokens.js CHANGED
@@ -8,6 +8,8 @@ export const HOST_AUDIENCE = "parlor:host";
8
8
  export const TURN_AUDIENCE = "parlor:mcp";
9
9
  export const HOST_ASSERTION_TYP = "parlor-host+jwt";
10
10
  export const TURN_TOKEN_TYP = "parlor-turn+jwt";
11
+ /** An agent on the host's computer outside any turn: Claude Code or Codex in a terminal. */
12
+ export const AGENT_TOKEN_TYP = "parlor-agent+jwt";
11
13
  const CLOCK_SKEW_MS = 60_000;
12
14
  /**
13
15
  * A turn token never outlives the revocation horizon, or a revoked grant's token could
@@ -44,9 +46,14 @@ export function credentialOf(authorization) {
44
46
  }
45
47
  if (!JWS_SHAPE.test(value))
46
48
  return { kind: "session", token: value };
47
- return typOf(value) === TURN_TOKEN_TYP
48
- ? { kind: "turn", token: brand(value) }
49
- : { kind: "access", token: value };
49
+ switch (typOf(value)) {
50
+ case TURN_TOKEN_TYP:
51
+ return { kind: "turn", token: brand(value) };
52
+ case AGENT_TOKEN_TYP:
53
+ return { kind: "agent", token: brand(value) };
54
+ default:
55
+ return { kind: "access", token: value };
56
+ }
50
57
  }
51
58
  /** Mints a turn token for `host`'s grant; the provider's `delegate`. */
52
59
  export async function delegate(keys, host, turn, now) {
@@ -131,4 +138,76 @@ export function agentPrincipalOf(claims) {
131
138
  },
132
139
  };
133
140
  }
141
+ /** Mints a token for an agent on `host`'s computer outside any turn; the provider's `delegateAgent`. */
142
+ export async function delegateAgent(keys, host, agent, now) {
143
+ if (!(agent.ttlMs > 0))
144
+ throw new RangeError("An agent token needs a positive lifetime.");
145
+ const iat = Math.floor(now / 1000);
146
+ const exp = iat + Math.ceil(Math.min(agent.ttlMs, MAX_TURN_TOKEN_TTL_MS) / 1000);
147
+ const claims = {
148
+ aud: TURN_AUDIENCE,
149
+ iss: keys.issuer,
150
+ sub: host.userId,
151
+ gid: host.grantId,
152
+ hid: host.hostId,
153
+ agt: agent.agent,
154
+ iat,
155
+ exp,
156
+ };
157
+ const token = await new SignJWT({ ...claims })
158
+ .setProtectedHeader({ alg: "ES256", typ: AGENT_TOKEN_TYP, kid: keys.kid })
159
+ .sign(keys.privateKey);
160
+ return { token, expiresAt: exp * 1000 };
161
+ }
162
+ const agentClaimsSchema = z.object({
163
+ aud: z.literal(TURN_AUDIENCE),
164
+ iss: z.string().min(1),
165
+ sub: z.string().min(1),
166
+ gid: z.string().min(1),
167
+ hid: z.string().min(1),
168
+ agt: z.string().min(1),
169
+ iat: z.number().int(),
170
+ exp: z.number().int(),
171
+ });
172
+ /** Signature, `aud`, `iss` and `exp` only; the caller then asks `RevocationSet` about `gid`. */
173
+ export async function verifyAgentToken(keys, token, now) {
174
+ let payload;
175
+ try {
176
+ ({ payload } = await jwtVerify(token, (header) => {
177
+ const key = header.kid === undefined ? undefined : keys.publicKeys.get(header.kid);
178
+ if (!key)
179
+ throw new errors.JWKSNoMatchingKey();
180
+ return key;
181
+ }, {
182
+ algorithms: ["ES256"],
183
+ typ: AGENT_TOKEN_TYP,
184
+ issuer: keys.issuer,
185
+ audience: TURN_AUDIENCE,
186
+ currentDate: new Date(now),
187
+ }));
188
+ }
189
+ catch (error) {
190
+ if (error instanceof errors.JWTExpired) {
191
+ throw new AuthFailure("expired", "The agent token expired.");
192
+ }
193
+ throw new AuthFailure("invalid", "The agent token is invalid.");
194
+ }
195
+ const claims = agentClaimsSchema.safeParse(payload);
196
+ if (!claims.success)
197
+ throw new AuthFailure("invalid", "The agent token is malformed.");
198
+ const { iat, exp } = claims.data;
199
+ if (exp - iat > MAX_TURN_TOKEN_TTL_MS / 1000 || iat * 1000 > now + CLOCK_SKEW_MS) {
200
+ throw new AuthFailure("invalid", "The agent token has an impossible lifetime.");
201
+ }
202
+ return claims.data;
203
+ }
204
+ /** An external agent of the user: no turn, so `ui_*` reaches no screen and confirms go to the app. */
205
+ export function outsideAgentPrincipalOf(claims) {
206
+ return {
207
+ kind: "agent",
208
+ userId: asId("user", claims.sub),
209
+ grantId: asId("grant", claims.gid),
210
+ agent: { name: claims.agt, verified: true },
211
+ };
212
+ }
134
213
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AgentPrincipal,\n asId,\n type HostPrincipal,\n type TurnBinding,\n} from \"@parlor/core\";\nimport {\n AuthFailure,\n type Delegation,\n REVOCATION_HORIZON_MS,\n type TurnDelegation,\n} from \"@parlor/server\";\nimport { decodeProtectedHeader, errors, jwtVerify, SignJWT } from \"jose\";\nimport { z } from \"zod\";\n\n/** A host assertion's audience: signed by the computer's own key, per request. */\nexport const HOST_AUDIENCE = \"parlor:host\";\n/** A turn token's audience: signed by the server, carried by the host's MCP proxy. */\nexport const TURN_AUDIENCE = \"parlor:mcp\";\n\nexport const HOST_ASSERTION_TYP = \"parlor-host+jwt\";\nexport const TURN_TOKEN_TYP = \"parlor-turn+jwt\";\n\nconst CLOCK_SKEW_MS = 60_000;\n\n/**\n * A turn token never outlives the revocation horizon, or a revoked grant's token could\n * verify after `RevocationSet` forgot the revocation.\n */\nexport const MAX_TURN_TOKEN_TTL_MS = REVOCATION_HORIZON_MS - CLOCK_SKEW_MS;\n\nexport type TokenAudience = typeof HOST_AUDIENCE | typeof TURN_AUDIENCE;\n\ndeclare const audience: unique symbol;\n\n/** A compact JWS whose audience is known from the scheme it arrived under; never swapped. */\nexport type Jws<A extends TokenAudience> = string & { readonly [audience]: A };\nexport type HostAssertion = Jws<typeof HOST_AUDIENCE>;\nexport type TurnToken = Jws<typeof TURN_AUDIENCE>;\n\n/** What an `authorization` header carries, sorted by scheme and shape before any verification. */\nexport type Credential =\n | { readonly kind: \"host\"; readonly assertion: HostAssertion }\n | { readonly kind: \"turn\"; readonly token: TurnToken }\n | { readonly kind: \"access\"; readonly token: string }\n | { readonly kind: \"session\"; readonly token: string };\n\nexport interface TurnTokenClaims {\n readonly aud: typeof TURN_AUDIENCE;\n readonly iss: string;\n /** user id */\n readonly sub: string;\n /** grant id: revoking the grant kills the token */\n readonly gid: string;\n readonly hid: string;\n readonly thr: string;\n readonly trn: string;\n /** the client the prompt came from; `ui_*` binds to it */\n readonly cid: string;\n readonly agt: string;\n readonly iat: number;\n /** as long as the turn's lease */\n readonly exp: number;\n}\n\nexport interface TurnTokenKeys {\n readonly issuer: string;\n readonly kid: string;\n readonly privateKey: CryptoKey;\n readonly publicKeys: ReadonlyMap<string, CryptoKey>;\n}\n\nconst JWS_SHAPE = /^[\\w-]+\\.[\\w-]+\\.[\\w-]+$/;\n\nfunction brand<A extends TokenAudience>(compact: string): Jws<A> {\n return compact as Jws<A>;\n}\n\nfunction typOf(jws: string): string | undefined {\n try {\n return decodeProtectedHeader(jws).typ;\n } catch {\n throw new AuthFailure(\"invalid\", \"The token is malformed.\");\n }\n}\n\n/** `ParlorHost <jws>` is a host; a `Bearer` JWS whose header says `typ: parlor-turn+jwt` is a turn. */\nexport function credentialOf(authorization: string | null): Credential | undefined {\n if (authorization === null) return undefined;\n const match = /^([A-Za-z]+)\\s+(\\S+)$/.exec(authorization);\n const scheme = match?.[1]?.toLowerCase();\n const value = match?.[2];\n if (value === undefined || (scheme !== \"bearer\" && scheme !== \"parlorhost\")) {\n throw new AuthFailure(\"invalid\", \"Use a Bearer token.\");\n }\n if (scheme === \"parlorhost\") {\n if (!JWS_SHAPE.test(value)) {\n throw new AuthFailure(\"invalid\", \"The host assertion is malformed.\");\n }\n return { kind: \"host\", assertion: brand<typeof HOST_AUDIENCE>(value) };\n }\n if (!JWS_SHAPE.test(value)) return { kind: \"session\", token: value };\n return typOf(value) === TURN_TOKEN_TYP\n ? { kind: \"turn\", token: brand<typeof TURN_AUDIENCE>(value) }\n : { kind: \"access\", token: value };\n}\n\n/** Mints a turn token for `host`'s grant; the provider's `delegate`. */\nexport async function delegate(\n keys: TurnTokenKeys,\n host: HostPrincipal,\n turn: TurnDelegation,\n now: number,\n): Promise<Delegation> {\n if (!(turn.ttlMs > 0)) throw new RangeError(\"A turn token needs a positive lifetime.\");\n const iat = Math.floor(now / 1000);\n const exp = iat + Math.ceil(Math.min(turn.ttlMs, MAX_TURN_TOKEN_TTL_MS) / 1000);\n const claims: TurnTokenClaims = {\n aud: TURN_AUDIENCE,\n iss: keys.issuer,\n sub: host.userId,\n gid: host.grantId,\n hid: host.hostId,\n thr: turn.threadId,\n trn: turn.turnId,\n cid: turn.clientId,\n agt: turn.agent,\n iat,\n exp,\n };\n const token = await new SignJWT({ ...claims })\n .setProtectedHeader({ alg: \"ES256\", typ: TURN_TOKEN_TYP, kid: keys.kid })\n .sign(keys.privateKey);\n return { token, expiresAt: exp * 1000 };\n}\n\nconst claimsSchema = z.object({\n aud: z.literal(TURN_AUDIENCE),\n iss: z.string().min(1),\n sub: z.string().min(1),\n gid: z.string().min(1),\n hid: z.string().min(1),\n thr: z.string().min(1),\n trn: z.string().min(1),\n cid: z.string(),\n agt: z.string().min(1),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\n/** Signature, `aud`, `iss` and `exp` only; the caller then asks `RevocationSet` about `gid`. */\nexport async function verifyTurnToken(\n keys: TurnTokenKeys,\n token: TurnToken,\n now: number,\n): Promise<TurnTokenClaims> {\n let payload: unknown;\n try {\n ({ payload } = await jwtVerify(\n token,\n (header) => {\n const key =\n header.kid === undefined ? undefined : keys.publicKeys.get(header.kid);\n if (!key) throw new errors.JWKSNoMatchingKey();\n return key;\n },\n {\n algorithms: [\"ES256\"],\n typ: TURN_TOKEN_TYP,\n issuer: keys.issuer,\n audience: TURN_AUDIENCE,\n currentDate: new Date(now),\n },\n ));\n } catch (error) {\n if (error instanceof errors.JWTExpired) {\n throw new AuthFailure(\"expired\", \"The turn token expired.\");\n }\n throw new AuthFailure(\"invalid\", \"The turn token is invalid.\");\n }\n const claims = claimsSchema.safeParse(payload);\n if (!claims.success) throw new AuthFailure(\"invalid\", \"The turn token is malformed.\");\n const { iat, exp } = claims.data;\n if (exp - iat > MAX_TURN_TOKEN_TTL_MS / 1000 || iat * 1000 > now + CLOCK_SKEW_MS) {\n throw new AuthFailure(\"invalid\", \"The turn token has an impossible lifetime.\");\n }\n return claims.data;\n}\n\nexport function agentPrincipalOf(\n claims: TurnTokenClaims,\n): AgentPrincipal & { readonly turn: TurnBinding } {\n return {\n kind: \"agent\",\n userId: asId(\"user\", claims.sub),\n grantId: asId(\"grant\", claims.gid),\n agent: { name: claims.agt, verified: true },\n turn: {\n threadId: asId(\"thread\", claims.thr),\n turnId: asId(\"turn\", claims.trn),\n hostId: asId(\"host\", claims.hid),\n clientId: claims.cid,\n },\n };\n}\n"]}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AgentPrincipal,\n asId,\n type HostPrincipal,\n type TurnBinding,\n} from \"@parlor/core\";\nimport {\n type AgentDelegation,\n AuthFailure,\n type Delegation,\n REVOCATION_HORIZON_MS,\n type TurnDelegation,\n} from \"@parlor/server\";\nimport { decodeProtectedHeader, errors, jwtVerify, SignJWT } from \"jose\";\nimport { z } from \"zod\";\n\n/** A host assertion's audience: signed by the computer's own key, per request. */\nexport const HOST_AUDIENCE = \"parlor:host\";\n/** A turn token's audience: signed by the server, carried by the host's MCP proxy. */\nexport const TURN_AUDIENCE = \"parlor:mcp\";\n\nexport const HOST_ASSERTION_TYP = \"parlor-host+jwt\";\nexport const TURN_TOKEN_TYP = \"parlor-turn+jwt\";\n/** An agent on the host's computer outside any turn: Claude Code or Codex in a terminal. */\nexport const AGENT_TOKEN_TYP = \"parlor-agent+jwt\";\n\nconst CLOCK_SKEW_MS = 60_000;\n\n/**\n * A turn token never outlives the revocation horizon, or a revoked grant's token could\n * verify after `RevocationSet` forgot the revocation.\n */\nexport const MAX_TURN_TOKEN_TTL_MS = REVOCATION_HORIZON_MS - CLOCK_SKEW_MS;\n\nexport type TokenAudience = typeof HOST_AUDIENCE | typeof TURN_AUDIENCE;\n\ndeclare const audience: unique symbol;\n\n/** A compact JWS whose audience is known from the scheme it arrived under; never swapped. */\nexport type Jws<A extends TokenAudience> = string & { readonly [audience]: A };\nexport type HostAssertion = Jws<typeof HOST_AUDIENCE>;\nexport type TurnToken = Jws<typeof TURN_AUDIENCE>;\ndeclare const outsideTurns: unique symbol;\n/** Same audience as a turn token, never mistaken for one: no turn, so no screen to drive. */\nexport type AgentToken = Jws<typeof TURN_AUDIENCE> & { readonly [outsideTurns]: true };\n\n/** What an `authorization` header carries, sorted by scheme and shape before any verification. */\nexport type Credential =\n | { readonly kind: \"host\"; readonly assertion: HostAssertion }\n | { readonly kind: \"turn\"; readonly token: TurnToken }\n | { readonly kind: \"agent\"; readonly token: AgentToken }\n | { readonly kind: \"access\"; readonly token: string }\n | { readonly kind: \"session\"; readonly token: string };\n\nexport interface TurnTokenClaims {\n readonly aud: typeof TURN_AUDIENCE;\n readonly iss: string;\n /** user id */\n readonly sub: string;\n /** grant id: revoking the grant kills the token */\n readonly gid: string;\n readonly hid: string;\n readonly thr: string;\n readonly trn: string;\n /** the client the prompt came from; `ui_*` binds to it */\n readonly cid: string;\n readonly agt: string;\n readonly iat: number;\n /** as long as the turn's lease */\n readonly exp: number;\n}\n\nexport interface TurnTokenKeys {\n readonly issuer: string;\n readonly kid: string;\n readonly privateKey: CryptoKey;\n readonly publicKeys: ReadonlyMap<string, CryptoKey>;\n}\n\nconst JWS_SHAPE = /^[\\w-]+\\.[\\w-]+\\.[\\w-]+$/;\n\nfunction brand<A extends TokenAudience>(compact: string): Jws<A> {\n return compact as Jws<A>;\n}\n\nfunction typOf(jws: string): string | undefined {\n try {\n return decodeProtectedHeader(jws).typ;\n } catch {\n throw new AuthFailure(\"invalid\", \"The token is malformed.\");\n }\n}\n\n/** `ParlorHost <jws>` is a host; a `Bearer` JWS whose header says `typ: parlor-turn+jwt` is a turn. */\nexport function credentialOf(authorization: string | null): Credential | undefined {\n if (authorization === null) return undefined;\n const match = /^([A-Za-z]+)\\s+(\\S+)$/.exec(authorization);\n const scheme = match?.[1]?.toLowerCase();\n const value = match?.[2];\n if (value === undefined || (scheme !== \"bearer\" && scheme !== \"parlorhost\")) {\n throw new AuthFailure(\"invalid\", \"Use a Bearer token.\");\n }\n if (scheme === \"parlorhost\") {\n if (!JWS_SHAPE.test(value)) {\n throw new AuthFailure(\"invalid\", \"The host assertion is malformed.\");\n }\n return { kind: \"host\", assertion: brand<typeof HOST_AUDIENCE>(value) };\n }\n if (!JWS_SHAPE.test(value)) return { kind: \"session\", token: value };\n switch (typOf(value)) {\n case TURN_TOKEN_TYP:\n return { kind: \"turn\", token: brand<typeof TURN_AUDIENCE>(value) };\n case AGENT_TOKEN_TYP:\n return { kind: \"agent\", token: brand<typeof TURN_AUDIENCE>(value) as AgentToken };\n default:\n return { kind: \"access\", token: value };\n }\n}\n\n/** Mints a turn token for `host`'s grant; the provider's `delegate`. */\nexport async function delegate(\n keys: TurnTokenKeys,\n host: HostPrincipal,\n turn: TurnDelegation,\n now: number,\n): Promise<Delegation> {\n if (!(turn.ttlMs > 0)) throw new RangeError(\"A turn token needs a positive lifetime.\");\n const iat = Math.floor(now / 1000);\n const exp = iat + Math.ceil(Math.min(turn.ttlMs, MAX_TURN_TOKEN_TTL_MS) / 1000);\n const claims: TurnTokenClaims = {\n aud: TURN_AUDIENCE,\n iss: keys.issuer,\n sub: host.userId,\n gid: host.grantId,\n hid: host.hostId,\n thr: turn.threadId,\n trn: turn.turnId,\n cid: turn.clientId,\n agt: turn.agent,\n iat,\n exp,\n };\n const token = await new SignJWT({ ...claims })\n .setProtectedHeader({ alg: \"ES256\", typ: TURN_TOKEN_TYP, kid: keys.kid })\n .sign(keys.privateKey);\n return { token, expiresAt: exp * 1000 };\n}\n\nconst claimsSchema = z.object({\n aud: z.literal(TURN_AUDIENCE),\n iss: z.string().min(1),\n sub: z.string().min(1),\n gid: z.string().min(1),\n hid: z.string().min(1),\n thr: z.string().min(1),\n trn: z.string().min(1),\n cid: z.string(),\n agt: z.string().min(1),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\n/** Signature, `aud`, `iss` and `exp` only; the caller then asks `RevocationSet` about `gid`. */\nexport async function verifyTurnToken(\n keys: TurnTokenKeys,\n token: TurnToken,\n now: number,\n): Promise<TurnTokenClaims> {\n let payload: unknown;\n try {\n ({ payload } = await jwtVerify(\n token,\n (header) => {\n const key =\n header.kid === undefined ? undefined : keys.publicKeys.get(header.kid);\n if (!key) throw new errors.JWKSNoMatchingKey();\n return key;\n },\n {\n algorithms: [\"ES256\"],\n typ: TURN_TOKEN_TYP,\n issuer: keys.issuer,\n audience: TURN_AUDIENCE,\n currentDate: new Date(now),\n },\n ));\n } catch (error) {\n if (error instanceof errors.JWTExpired) {\n throw new AuthFailure(\"expired\", \"The turn token expired.\");\n }\n throw new AuthFailure(\"invalid\", \"The turn token is invalid.\");\n }\n const claims = claimsSchema.safeParse(payload);\n if (!claims.success) throw new AuthFailure(\"invalid\", \"The turn token is malformed.\");\n const { iat, exp } = claims.data;\n if (exp - iat > MAX_TURN_TOKEN_TTL_MS / 1000 || iat * 1000 > now + CLOCK_SKEW_MS) {\n throw new AuthFailure(\"invalid\", \"The turn token has an impossible lifetime.\");\n }\n return claims.data;\n}\n\nexport function agentPrincipalOf(\n claims: TurnTokenClaims,\n): AgentPrincipal & { readonly turn: TurnBinding } {\n return {\n kind: \"agent\",\n userId: asId(\"user\", claims.sub),\n grantId: asId(\"grant\", claims.gid),\n agent: { name: claims.agt, verified: true },\n turn: {\n threadId: asId(\"thread\", claims.thr),\n turnId: asId(\"turn\", claims.trn),\n hostId: asId(\"host\", claims.hid),\n clientId: claims.cid,\n },\n };\n}\n\nexport interface AgentTokenClaims {\n readonly aud: typeof TURN_AUDIENCE;\n readonly iss: string;\n readonly sub: string;\n readonly gid: string;\n readonly hid: string;\n readonly agt: string;\n readonly iat: number;\n readonly exp: number;\n}\n\n/** Mints a token for an agent on `host`'s computer outside any turn; the provider's `delegateAgent`. */\nexport async function delegateAgent(\n keys: TurnTokenKeys,\n host: HostPrincipal,\n agent: AgentDelegation,\n now: number,\n): Promise<Delegation> {\n if (!(agent.ttlMs > 0))\n throw new RangeError(\"An agent token needs a positive lifetime.\");\n const iat = Math.floor(now / 1000);\n const exp = iat + Math.ceil(Math.min(agent.ttlMs, MAX_TURN_TOKEN_TTL_MS) / 1000);\n const claims: AgentTokenClaims = {\n aud: TURN_AUDIENCE,\n iss: keys.issuer,\n sub: host.userId,\n gid: host.grantId,\n hid: host.hostId,\n agt: agent.agent,\n iat,\n exp,\n };\n const token = await new SignJWT({ ...claims })\n .setProtectedHeader({ alg: \"ES256\", typ: AGENT_TOKEN_TYP, kid: keys.kid })\n .sign(keys.privateKey);\n return { token, expiresAt: exp * 1000 };\n}\n\nconst agentClaimsSchema = z.object({\n aud: z.literal(TURN_AUDIENCE),\n iss: z.string().min(1),\n sub: z.string().min(1),\n gid: z.string().min(1),\n hid: z.string().min(1),\n agt: z.string().min(1),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\n/** Signature, `aud`, `iss` and `exp` only; the caller then asks `RevocationSet` about `gid`. */\nexport async function verifyAgentToken(\n keys: TurnTokenKeys,\n token: AgentToken,\n now: number,\n): Promise<AgentTokenClaims> {\n let payload: unknown;\n try {\n ({ payload } = await jwtVerify(\n token,\n (header) => {\n const key =\n header.kid === undefined ? undefined : keys.publicKeys.get(header.kid);\n if (!key) throw new errors.JWKSNoMatchingKey();\n return key;\n },\n {\n algorithms: [\"ES256\"],\n typ: AGENT_TOKEN_TYP,\n issuer: keys.issuer,\n audience: TURN_AUDIENCE,\n currentDate: new Date(now),\n },\n ));\n } catch (error) {\n if (error instanceof errors.JWTExpired) {\n throw new AuthFailure(\"expired\", \"The agent token expired.\");\n }\n throw new AuthFailure(\"invalid\", \"The agent token is invalid.\");\n }\n const claims = agentClaimsSchema.safeParse(payload);\n if (!claims.success) throw new AuthFailure(\"invalid\", \"The agent token is malformed.\");\n const { iat, exp } = claims.data;\n if (exp - iat > MAX_TURN_TOKEN_TTL_MS / 1000 || iat * 1000 > now + CLOCK_SKEW_MS) {\n throw new AuthFailure(\"invalid\", \"The agent token has an impossible lifetime.\");\n }\n return claims.data;\n}\n\n/** An external agent of the user: no turn, so `ui_*` reaches no screen and confirms go to the app. */\nexport function outsideAgentPrincipalOf(claims: AgentTokenClaims): AgentPrincipal {\n return {\n kind: \"agent\",\n userId: asId(\"user\", claims.sub),\n grantId: asId(\"grant\", claims.gid),\n agent: { name: claims.agt, verified: true },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parlor/auth",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "homepage": "https://cychien.github.io/parlor",
5
5
  "bugs": "https://github.com/cychien/parlor/issues",
6
6
  "license": "MIT",
@@ -36,11 +36,11 @@
36
36
  "drizzle-orm": "^0.45.4",
37
37
  "jose": "^6.2.12",
38
38
  "zod": "^4.6.5",
39
- "@parlor/capabilities": "0.2.0",
40
- "@parlor/chat": "0.2.0",
41
- "@parlor/core": "0.2.0",
42
- "@parlor/server": "0.2.0",
43
- "@parlor/client": "0.2.0"
39
+ "@parlor/core": "0.3.1",
40
+ "@parlor/server": "0.3.1",
41
+ "@parlor/chat": "0.3.1",
42
+ "@parlor/capabilities": "0.3.1",
43
+ "@parlor/client": "0.3.1"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@modelcontextprotocol/client": "^2.3.1",
@@ -50,7 +50,7 @@
50
50
  "pg": "^8.23.1",
51
51
  "typescript": "^7.0.2",
52
52
  "vitest": "^5.0.3",
53
- "@parlor/data": "0.2.0"
53
+ "@parlor/data": "0.3.1"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsc -p tsconfig.json",