@parlor/server 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +19 -2
- package/dist/access.d.ts +27 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +41 -0
- package/dist/access.js.map +1 -0
- package/dist/actions.d.ts +43 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +169 -0
- package/dist/actions.js.map +1 -0
- package/dist/auth.d.ts +97 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +11 -0
- package/dist/auth.js.map +1 -0
- package/dist/body.d.ts +7 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +29 -0
- package/dist/body.js.map +1 -0
- package/dist/caller.d.ts +21 -0
- package/dist/caller.d.ts.map +1 -0
- package/dist/caller.js +46 -0
- package/dist/caller.js.map +1 -0
- package/dist/cors.d.ts +9 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +25 -0
- package/dist/cors.js.map +1 -0
- package/dist/create-server.d.ts +43 -0
- package/dist/create-server.d.ts.map +1 -0
- package/dist/create-server.js +422 -0
- package/dist/create-server.js.map +1 -0
- package/dist/dev-auth.d.ts +17 -0
- package/dist/dev-auth.d.ts.map +1 -0
- package/dist/dev-auth.js +146 -0
- package/dist/dev-auth.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-calls.d.ts +7 -0
- package/dist/mcp-calls.d.ts.map +1 -0
- package/dist/mcp-calls.js +30 -0
- package/dist/mcp-calls.js.map +1 -0
- package/dist/mcp.d.ts +29 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +198 -0
- package/dist/mcp.js.map +1 -0
- package/dist/migrations.d.ts +10 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +81 -0
- package/dist/migrations.js.map +1 -0
- package/dist/plugin.d.ts +117 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +5 -0
- package/dist/plugin.js.map +1 -0
- package/dist/protocol.d.ts +18 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +7 -0
- package/dist/protocol.js.map +1 -0
- package/dist/revocation-horizon.d.ts +6 -0
- package/dist/revocation-horizon.d.ts.map +1 -0
- package/dist/revocation-horizon.js +6 -0
- package/dist/revocation-horizon.js.map +1 -0
- package/dist/scope.d.ts +6 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +4 -0
- package/dist/scope.js.map +1 -0
- package/package.json +41 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cychien
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @parlor/server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Parlor server: every action over HTTP and MCP, live updates, auth and plugins, as one web-standard `fetch` handler.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add @parlor/server @parlor/core @parlor/data
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createRegistry } from "@parlor/core/registry";
|
|
11
|
+
import { createDatabase } from "@parlor/data";
|
|
12
|
+
import { createServer } from "@parlor/server";
|
|
13
|
+
|
|
14
|
+
const registry = createRegistry({});
|
|
15
|
+
const database = await createDatabase({ migrationsFolder: "migrations" });
|
|
16
|
+
|
|
17
|
+
export default createServer({ actions: registry, database });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`parlor dev` serves it; `parlor build` wraps it as a Node or Bun server. Docs: https://cychien.github.io/parlor/reference/server/
|
package/dist/access.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Principal, type PrincipalKind } from "@parlor/core";
|
|
2
|
+
import type { Caller } from "./caller.js";
|
|
3
|
+
export declare const ROUTE_ACCESS: {
|
|
4
|
+
readonly manifest: readonly ["user", "agent"];
|
|
5
|
+
readonly actions: readonly ["user", "agent"];
|
|
6
|
+
readonly pluginActions: readonly ["user", "host"];
|
|
7
|
+
readonly mcp: readonly ["agent"];
|
|
8
|
+
readonly sync: readonly ["user", "host"];
|
|
9
|
+
readonly channels: readonly ["user"];
|
|
10
|
+
readonly ui: readonly ["user"];
|
|
11
|
+
};
|
|
12
|
+
export type Surface = keyof typeof ROUTE_ACCESS;
|
|
13
|
+
/** An anonymous caller reaches every surface while no enforcing provider is installed. */
|
|
14
|
+
export type KindsOf<S extends Surface> = "anonymous" | (typeof ROUTE_ACCESS)[S][number];
|
|
15
|
+
export type CallerOf<K extends PrincipalKind> = Caller & {
|
|
16
|
+
readonly principal: Extract<Principal, {
|
|
17
|
+
readonly kind: K;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export declare function admits(surface: Surface, caller: Caller, enforcing: boolean): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Narrows a caller to the kinds a surface admits, or refuses it: `unauthenticated` for an
|
|
23
|
+
* anonymous caller, `forbidden` for a signed-in principal of another kind.
|
|
24
|
+
*/
|
|
25
|
+
export declare function requireSurface<S extends Surface>(caller: Caller, surface: S, enforcing: boolean): CallerOf<KindsOf<S>>;
|
|
26
|
+
export declare function requireAccess<K extends PrincipalKind>(caller: Caller, kinds: readonly K[], enforcing: boolean): CallerOf<K>;
|
|
27
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,eAAO,MAAM,YAAY;;;;;;;;CAQmC,CAAC;AAE7D,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,YAAY,CAAC;AAKhD,0FAA0F;AAC1F,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,OAAO,IAAI,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAExF,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,aAAa,IAAI,MAAM,GAAG;IACvD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC9D,CAAC;AAEF,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAIpF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAC9C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,EACV,SAAS,EAAE,OAAO,GACjB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAKtB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EACnD,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,SAAS,EAAE,OAAO,GACjB,QAAQ,CAAC,CAAC,CAAC,CAQb"}
|
package/dist/access.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ActionError, } from "@parlor/core";
|
|
2
|
+
export const ROUTE_ACCESS = {
|
|
3
|
+
manifest: ["user", "agent"],
|
|
4
|
+
actions: ["user", "agent"],
|
|
5
|
+
pluginActions: ["user", "host"],
|
|
6
|
+
mcp: ["agent"],
|
|
7
|
+
sync: ["user", "host"],
|
|
8
|
+
channels: ["user"],
|
|
9
|
+
ui: ["user"],
|
|
10
|
+
};
|
|
11
|
+
/** Reached by anonymous callers even under an enforcing provider; each action checks `public`. */
|
|
12
|
+
const OPEN_WHEN_ENFORCING = new Set(["actions"]);
|
|
13
|
+
export function admits(surface, caller, enforcing) {
|
|
14
|
+
const { kind } = caller.principal;
|
|
15
|
+
if (kind === "anonymous")
|
|
16
|
+
return !enforcing || OPEN_WHEN_ENFORCING.has(surface);
|
|
17
|
+
return ROUTE_ACCESS[surface].includes(kind);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Narrows a caller to the kinds a surface admits, or refuses it: `unauthenticated` for an
|
|
21
|
+
* anonymous caller, `forbidden` for a signed-in principal of another kind.
|
|
22
|
+
*/
|
|
23
|
+
export function requireSurface(caller, surface, enforcing) {
|
|
24
|
+
if (admits(surface, caller, enforcing))
|
|
25
|
+
return caller;
|
|
26
|
+
const { kind } = caller.principal;
|
|
27
|
+
if (kind === "anonymous")
|
|
28
|
+
throw new ActionError("unauthenticated", "Sign in first.");
|
|
29
|
+
throw new ActionError("forbidden", `A ${kind} principal cannot use ${surface}.`);
|
|
30
|
+
}
|
|
31
|
+
export function requireAccess(caller, kinds, enforcing) {
|
|
32
|
+
const { kind } = caller.principal;
|
|
33
|
+
const listed = kinds.includes(kind);
|
|
34
|
+
if (kind === "anonymous" && (enforcing || !listed)) {
|
|
35
|
+
throw new ActionError("unauthenticated", "Sign in first.");
|
|
36
|
+
}
|
|
37
|
+
if (!listed)
|
|
38
|
+
throw new ActionError("forbidden", `A ${kind} principal cannot use this.`);
|
|
39
|
+
return caller;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,GAIZ,MAAM,cAAc,CAAC;AAMtB,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IAC/B,GAAG,EAAE,CAAC,OAAO,CAAC;IACd,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,CAAC;IAClB,EAAE,EAAE,CAAC,MAAM,CAAC;CAC8C,CAAC;AAI7D,kGAAkG;AAClG,MAAM,mBAAmB,GAAyB,IAAI,GAAG,CAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AAShF,MAAM,UAAU,MAAM,CAAC,OAAgB,EAAE,MAAc,EAAE,SAAkB;IACzE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAClC,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,CAAC,SAAS,IAAI,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChF,OAAQ,YAAY,CAAC,OAAO,CAA6B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,OAAU,EACV,SAAkB;IAElB,IAAI,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC;QAAE,OAAO,MAA8B,CAAC;IAC9E,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAClC,IAAI,IAAI,KAAK,WAAW;QAAE,MAAM,IAAI,WAAW,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;IACrF,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,KAAK,IAAI,yBAAyB,OAAO,GAAG,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,KAAmB,EACnB,SAAkB;IAElB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAClC,MAAM,MAAM,GAAI,KAAkC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClE,IAAI,IAAI,KAAK,WAAW,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,WAAW,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,KAAK,IAAI,6BAA6B,CAAC,CAAC;IACxF,OAAO,MAAqB,CAAC;AAC/B,CAAC","sourcesContent":["import {\n ActionError,\n type AuthenticatedPrincipal,\n type Principal,\n type PrincipalKind,\n} from \"@parlor/core\";\n\nimport type { Caller } from \"./caller.js\";\n\ntype SignedInKind = AuthenticatedPrincipal[\"kind\"];\n\nexport const ROUTE_ACCESS = {\n manifest: [\"user\", \"agent\"],\n actions: [\"user\", \"agent\"],\n pluginActions: [\"user\", \"host\"],\n mcp: [\"agent\"],\n sync: [\"user\", \"host\"],\n channels: [\"user\"],\n ui: [\"user\"],\n} as const satisfies Record<string, readonly SignedInKind[]>;\n\nexport type Surface = keyof typeof ROUTE_ACCESS;\n\n/** Reached by anonymous callers even under an enforcing provider; each action checks `public`. */\nconst OPEN_WHEN_ENFORCING: ReadonlySet<Surface> = new Set<Surface>([\"actions\"]);\n\n/** An anonymous caller reaches every surface while no enforcing provider is installed. */\nexport type KindsOf<S extends Surface> = \"anonymous\" | (typeof ROUTE_ACCESS)[S][number];\n\nexport type CallerOf<K extends PrincipalKind> = Caller & {\n readonly principal: Extract<Principal, { readonly kind: K }>;\n};\n\nexport function admits(surface: Surface, caller: Caller, enforcing: boolean): boolean {\n const { kind } = caller.principal;\n if (kind === \"anonymous\") return !enforcing || OPEN_WHEN_ENFORCING.has(surface);\n return (ROUTE_ACCESS[surface] as readonly SignedInKind[]).includes(kind);\n}\n\n/**\n * Narrows a caller to the kinds a surface admits, or refuses it: `unauthenticated` for an\n * anonymous caller, `forbidden` for a signed-in principal of another kind.\n */\nexport function requireSurface<S extends Surface>(\n caller: Caller,\n surface: S,\n enforcing: boolean,\n): CallerOf<KindsOf<S>> {\n if (admits(surface, caller, enforcing)) return caller as CallerOf<KindsOf<S>>;\n const { kind } = caller.principal;\n if (kind === \"anonymous\") throw new ActionError(\"unauthenticated\", \"Sign in first.\");\n throw new ActionError(\"forbidden\", `A ${kind} principal cannot use ${surface}.`);\n}\n\nexport function requireAccess<K extends PrincipalKind>(\n caller: Caller,\n kinds: readonly K[],\n enforcing: boolean,\n): CallerOf<K> {\n const { kind } = caller.principal;\n const listed = (kinds as readonly PrincipalKind[]).includes(kind);\n if (kind === \"anonymous\" && (enforcing || !listed)) {\n throw new ActionError(\"unauthenticated\", \"Sign in first.\");\n }\n if (!listed) throw new ActionError(\"forbidden\", `A ${kind} principal cannot use this.`);\n return caller as CallerOf<K>;\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AgentOrigin } from "@parlor/agent-ui";
|
|
2
|
+
import type { ConfirmationOutcome, ConfirmationRequest } from "@parlor/agent-ui/server";
|
|
3
|
+
import { type ActionMap, type ActionSource, type DataProvider } from "@parlor/core";
|
|
4
|
+
import { type ActionRegistry, type InvokeResult } from "@parlor/core/registry";
|
|
5
|
+
import { type Surface } from "./access.js";
|
|
6
|
+
import { type Caller, type CallerResolver } from "./caller.js";
|
|
7
|
+
import type { ActionAudit } from "./plugin.js";
|
|
8
|
+
import { type ScopeResolver } from "./scope.js";
|
|
9
|
+
export { clientOf, sourceOf } from "./caller.js";
|
|
10
|
+
export type Confirm = (request: ConfirmationRequest) => Promise<ConfirmationOutcome>;
|
|
11
|
+
export interface ActionRunnerOptions<T extends ActionMap> {
|
|
12
|
+
readonly actions: ActionRegistry<T>;
|
|
13
|
+
readonly scope: ScopeResolver;
|
|
14
|
+
readonly database?: DataProvider | undefined;
|
|
15
|
+
readonly sync: boolean;
|
|
16
|
+
/** Resolves each request's identity once; without it every caller is anonymous. */
|
|
17
|
+
readonly callers?: CallerResolver | undefined;
|
|
18
|
+
readonly onClient?: ((clientId: string | undefined, audience: string) => void) | undefined;
|
|
19
|
+
readonly confirm?: Confirm | undefined;
|
|
20
|
+
readonly audit?: ((record: ActionAudit) => void) | undefined;
|
|
21
|
+
/** Throws for a caller the surface admits but the actions' owner does not; audited like any refusal. */
|
|
22
|
+
readonly admit?: ((caller: Caller) => Promise<void>) | undefined;
|
|
23
|
+
/** Awaited before every call, so it must memoize; defaults to creating the sync tables. */
|
|
24
|
+
readonly ready?: (() => Promise<void>) | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface RunOptions {
|
|
27
|
+
readonly signal?: AbortSignal | undefined;
|
|
28
|
+
readonly origin?: AgentOrigin | undefined;
|
|
29
|
+
/** Refuses callers the surface does not admit, inside the call so the refusal is audited. */
|
|
30
|
+
readonly surface?: Surface | undefined;
|
|
31
|
+
/** Also answer the sync cursor a read's result reflects, for a client that keeps it fresh. */
|
|
32
|
+
readonly cursor?: boolean | undefined;
|
|
33
|
+
}
|
|
34
|
+
export interface ActionRun extends InvokeResult {
|
|
35
|
+
/** Set for a read with `cursor`: every sync event of the caller's streams up to it is in `data`. */
|
|
36
|
+
readonly cursor?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface ActionRunner {
|
|
39
|
+
run(name: string, input: unknown, request: Request | undefined, source: ActionSource, options?: RunOptions): Promise<ActionRun>;
|
|
40
|
+
ready(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createActionRunner<T extends ActionMap>(options: ActionRunnerOptions<T>): ActionRunner;
|
|
43
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { ActionError, ANONYMOUS, isMutating, } from "@parlor/core";
|
|
2
|
+
import { invokeAction, } from "@parlor/core/registry";
|
|
3
|
+
import { encodeCursor } from "@parlor/sync";
|
|
4
|
+
import { createPruner, ensureSyncTables, readSyncCursor, recordSyncEvent, } from "@parlor/sync/server";
|
|
5
|
+
import { requireSurface } from "./access.js";
|
|
6
|
+
import { AuthFailure } from "./auth.js";
|
|
7
|
+
import { createCallerResolver, sourceFor, } from "./caller.js";
|
|
8
|
+
import { DEFAULT_SCOPE } from "./scope.js";
|
|
9
|
+
export { clientOf, sourceOf } from "./caller.js";
|
|
10
|
+
function originOf(source, principal) {
|
|
11
|
+
const via = source === "agent" ? "agent" : "mcp";
|
|
12
|
+
return principal.kind === "agent" ? { via, name: principal.agent.name } : { via };
|
|
13
|
+
}
|
|
14
|
+
function confirmationGate(gate) {
|
|
15
|
+
return async ({ action, name, input, ctx }) => {
|
|
16
|
+
const turn = ctx.principal?.kind === "agent" ? ctx.principal.turn : undefined;
|
|
17
|
+
const outcome = await gate.confirm({
|
|
18
|
+
scope: gate.scope,
|
|
19
|
+
clientId: turn?.clientId ?? gate.clientId,
|
|
20
|
+
...(turn ? { thread: { threadId: turn.threadId, turnId: turn.turnId } } : {}),
|
|
21
|
+
action: name,
|
|
22
|
+
description: action.description,
|
|
23
|
+
input,
|
|
24
|
+
origin: gate.origin ?? originOf(ctx.source, ctx.principal ?? ANONYMOUS),
|
|
25
|
+
signal: gate.signal,
|
|
26
|
+
});
|
|
27
|
+
if (outcome !== "cancelled") {
|
|
28
|
+
gate.answered(outcome === "shutdown" ? "unavailable" : outcome);
|
|
29
|
+
}
|
|
30
|
+
switch (outcome) {
|
|
31
|
+
case "approved":
|
|
32
|
+
return;
|
|
33
|
+
case "declined":
|
|
34
|
+
throw new ActionError("declined", `The user declined ${name} in the app; nothing ran. Do not retry unless they ask again.`);
|
|
35
|
+
case "timeout":
|
|
36
|
+
throw new ActionError("confirmation_timeout", `The user did not answer in time; ${name} did not run.`);
|
|
37
|
+
case "unavailable":
|
|
38
|
+
throw new ActionError("confirmation_unavailable", `No open app can ask the user to confirm ${name}. Ask the user to do it in the app themselves.`);
|
|
39
|
+
case "shutdown":
|
|
40
|
+
throw new ActionError("confirmation_unavailable", `The server shut down before the user answered; ${name} did not run. Try again once it is back, or ask the user to do it in the app themselves.`);
|
|
41
|
+
case "cancelled":
|
|
42
|
+
throw (gate.signal?.reason ?? new DOMException("The call was cancelled.", "AbortError"));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const INTERNAL_CALLER = {
|
|
47
|
+
principal: ANONYMOUS,
|
|
48
|
+
scope: DEFAULT_SCOPE,
|
|
49
|
+
audience: DEFAULT_SCOPE,
|
|
50
|
+
clientId: undefined,
|
|
51
|
+
};
|
|
52
|
+
export function createActionRunner(options) {
|
|
53
|
+
const { actions, scope, database, sync, onClient, confirm, audit, admit } = options;
|
|
54
|
+
const callers = options.callers ?? createCallerResolver(undefined, scope);
|
|
55
|
+
const pruner = sync && database ? createPruner(database) : undefined;
|
|
56
|
+
let prepared;
|
|
57
|
+
const ready = options.ready ??
|
|
58
|
+
(() => {
|
|
59
|
+
prepared ??=
|
|
60
|
+
sync && database
|
|
61
|
+
? ensureSyncTables(database.read())
|
|
62
|
+
: Promise.resolve();
|
|
63
|
+
return prepared;
|
|
64
|
+
});
|
|
65
|
+
const cursorBefore = async (name, caller) => {
|
|
66
|
+
const action = actions.get(name);
|
|
67
|
+
if (!sync || !database || !action?.reads || isMutating(action))
|
|
68
|
+
return undefined;
|
|
69
|
+
const streams = caller.audience === caller.scope ? [caller.scope] : [caller.scope, caller.audience];
|
|
70
|
+
return encodeCursor(await readSyncCursor(database.read(), streams));
|
|
71
|
+
};
|
|
72
|
+
const invoke = async (name, input, request, caller, source, runOptions, trace) => {
|
|
73
|
+
if (runOptions.surface)
|
|
74
|
+
requireSurface(caller, runOptions.surface, callers.enforcing);
|
|
75
|
+
await admit?.(caller);
|
|
76
|
+
const client = caller.clientId;
|
|
77
|
+
onClient?.(client, caller.audience);
|
|
78
|
+
const signal = runOptions.signal ?? request?.signal;
|
|
79
|
+
const resolvedScope = caller.scope;
|
|
80
|
+
const cursor = runOptions.cursor ? await cursorBefore(name, caller) : undefined;
|
|
81
|
+
const result = await invokeAction(actions, name, input, { scope: resolvedScope, source, principal: caller.principal, signal, request }, {
|
|
82
|
+
data: database,
|
|
83
|
+
enforcing: callers.enforcing,
|
|
84
|
+
onRun: () => {
|
|
85
|
+
trace.stage = "ran";
|
|
86
|
+
},
|
|
87
|
+
confirm: confirm
|
|
88
|
+
? confirmationGate({
|
|
89
|
+
confirm,
|
|
90
|
+
answered: (answer) => {
|
|
91
|
+
trace.confirmation = answer;
|
|
92
|
+
},
|
|
93
|
+
scope: caller.audience,
|
|
94
|
+
clientId: client,
|
|
95
|
+
origin: runOptions.origin,
|
|
96
|
+
signal,
|
|
97
|
+
})
|
|
98
|
+
: undefined,
|
|
99
|
+
onWrite: sync && database
|
|
100
|
+
? async ({ action, tags, tx }) => {
|
|
101
|
+
if (tags.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
await recordSyncEvent(tx, {
|
|
104
|
+
scope: action.notifies === "user" ? caller.audience : resolvedScope,
|
|
105
|
+
tags,
|
|
106
|
+
source: client,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
: undefined,
|
|
110
|
+
});
|
|
111
|
+
if (result.writes.length > 0)
|
|
112
|
+
pruner?.touch();
|
|
113
|
+
return cursor === undefined ? result : { ...result, cursor };
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
ready,
|
|
117
|
+
async run(name, input, request, declared, runOptions = {}) {
|
|
118
|
+
await ready();
|
|
119
|
+
const caller = request ? await callers(request) : INTERNAL_CALLER;
|
|
120
|
+
const source = sourceFor(declared, caller, callers.enforcing);
|
|
121
|
+
const trace = { stage: "refused", confirmation: undefined };
|
|
122
|
+
if (!audit)
|
|
123
|
+
return invoke(name, input, request, caller, source, runOptions, trace);
|
|
124
|
+
const at = Date.now();
|
|
125
|
+
const record = async (outcome) => {
|
|
126
|
+
try {
|
|
127
|
+
audit({
|
|
128
|
+
at,
|
|
129
|
+
action: name,
|
|
130
|
+
principal: caller.principal,
|
|
131
|
+
source,
|
|
132
|
+
scope: caller.scope,
|
|
133
|
+
inputDigest: await digest(input),
|
|
134
|
+
stage: trace.stage,
|
|
135
|
+
outcome,
|
|
136
|
+
...(trace.confirmation ? { confirmation: trace.confirmation } : {}),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error("[parlor] audit failed:", error);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
const result = await invoke(name, input, request, caller, source, runOptions, trace);
|
|
145
|
+
await record("ok");
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
await record(outcomeOf(error));
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function outcomeOf(error) {
|
|
156
|
+
if (error instanceof ActionError)
|
|
157
|
+
return error.code;
|
|
158
|
+
if (error instanceof AuthFailure)
|
|
159
|
+
return "unauthenticated";
|
|
160
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
161
|
+
return "cancelled";
|
|
162
|
+
return "internal";
|
|
163
|
+
}
|
|
164
|
+
async function digest(input) {
|
|
165
|
+
const bytes = new TextEncoder().encode(JSON.stringify(input ?? null));
|
|
166
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
167
|
+
return [...new Uint8Array(hash)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentOrigin } from \"@parlor/agent-ui\";\nimport type { ConfirmationOutcome, ConfirmationRequest } from \"@parlor/agent-ui/server\";\nimport {\n ActionError,\n type ActionMap,\n ANONYMOUS,\n type ActionSource,\n type DataProvider,\n isMutating,\n type Principal,\n} from \"@parlor/core\";\nimport {\n type ActionRegistry,\n invokeAction,\n type InvokeResult,\n type PreparedCall,\n} from \"@parlor/core/registry\";\nimport { encodeCursor } from \"@parlor/sync\";\nimport {\n createPruner,\n ensureSyncTables,\n readSyncCursor,\n recordSyncEvent,\n type SyncDatabase,\n} from \"@parlor/sync/server\";\n\nimport { requireSurface, type Surface } from \"./access.js\";\nimport { AuthFailure } from \"./auth.js\";\nimport {\n type Caller,\n type CallerResolver,\n createCallerResolver,\n sourceFor,\n} from \"./caller.js\";\nimport type { ActionAudit, ConfirmationAnswer } from \"./plugin.js\";\nimport { DEFAULT_SCOPE, type ScopeResolver } from \"./scope.js\";\n\nexport { clientOf, sourceOf } from \"./caller.js\";\n\nexport type Confirm = (request: ConfirmationRequest) => Promise<ConfirmationOutcome>;\n\nexport interface ActionRunnerOptions<T extends ActionMap> {\n readonly actions: ActionRegistry<T>;\n readonly scope: ScopeResolver;\n readonly database?: DataProvider | undefined;\n readonly sync: boolean;\n /** Resolves each request's identity once; without it every caller is anonymous. */\n readonly callers?: CallerResolver | undefined;\n readonly onClient?:\n | ((clientId: string | undefined, audience: string) => void)\n | undefined;\n readonly confirm?: Confirm | undefined;\n readonly audit?: ((record: ActionAudit) => void) | undefined;\n /** Throws for a caller the surface admits but the actions' owner does not; audited like any refusal. */\n readonly admit?: ((caller: Caller) => Promise<void>) | undefined;\n /** Awaited before every call, so it must memoize; defaults to creating the sync tables. */\n readonly ready?: (() => Promise<void>) | undefined;\n}\n\nexport interface RunOptions {\n readonly signal?: AbortSignal | undefined;\n readonly origin?: AgentOrigin | undefined;\n /** Refuses callers the surface does not admit, inside the call so the refusal is audited. */\n readonly surface?: Surface | undefined;\n /** Also answer the sync cursor a read's result reflects, for a client that keeps it fresh. */\n readonly cursor?: boolean | undefined;\n}\n\nexport interface ActionRun extends InvokeResult {\n /** Set for a read with `cursor`: every sync event of the caller's streams up to it is in `data`. */\n readonly cursor?: string | undefined;\n}\n\nexport interface ActionRunner {\n run(\n name: string,\n input: unknown,\n request: Request | undefined,\n source: ActionSource,\n options?: RunOptions,\n ): Promise<ActionRun>;\n ready(): Promise<void>;\n}\n\ninterface Gate {\n readonly confirm: Confirm;\n readonly answered: (answer: ConfirmationAnswer) => void;\n readonly scope: string;\n readonly clientId: string | undefined;\n readonly origin: AgentOrigin | undefined;\n readonly signal: AbortSignal | undefined;\n}\n\nfunction originOf(source: ActionSource, principal: Principal): AgentOrigin {\n const via = source === \"agent\" ? \"agent\" : \"mcp\";\n return principal.kind === \"agent\" ? { via, name: principal.agent.name } : { via };\n}\n\nfunction confirmationGate(gate: Gate): (call: PreparedCall) => Promise<void> {\n return async ({ action, name, input, ctx }) => {\n const turn = ctx.principal?.kind === \"agent\" ? ctx.principal.turn : undefined;\n const outcome = await gate.confirm({\n scope: gate.scope,\n clientId: turn?.clientId ?? gate.clientId,\n ...(turn ? { thread: { threadId: turn.threadId, turnId: turn.turnId } } : {}),\n action: name,\n description: action.description,\n input,\n origin: gate.origin ?? originOf(ctx.source, ctx.principal ?? ANONYMOUS),\n signal: gate.signal,\n });\n if (outcome !== \"cancelled\") {\n gate.answered(outcome === \"shutdown\" ? \"unavailable\" : outcome);\n }\n switch (outcome) {\n case \"approved\":\n return;\n case \"declined\":\n throw new ActionError(\n \"declined\",\n `The user declined ${name} in the app; nothing ran. Do not retry unless they ask again.`,\n );\n case \"timeout\":\n throw new ActionError(\n \"confirmation_timeout\",\n `The user did not answer in time; ${name} did not run.`,\n );\n case \"unavailable\":\n throw new ActionError(\n \"confirmation_unavailable\",\n `No open app can ask the user to confirm ${name}. Ask the user to do it in the app themselves.`,\n );\n case \"shutdown\":\n throw new ActionError(\n \"confirmation_unavailable\",\n `The server shut down before the user answered; ${name} did not run. Try again once it is back, or ask the user to do it in the app themselves.`,\n );\n case \"cancelled\":\n throw (\n gate.signal?.reason ?? new DOMException(\"The call was cancelled.\", \"AbortError\")\n );\n }\n };\n}\n\nconst INTERNAL_CALLER: Caller = {\n principal: ANONYMOUS,\n scope: DEFAULT_SCOPE,\n audience: DEFAULT_SCOPE,\n clientId: undefined,\n};\n\nexport function createActionRunner<T extends ActionMap>(\n options: ActionRunnerOptions<T>,\n): ActionRunner {\n const { actions, scope, database, sync, onClient, confirm, audit, admit } = options;\n const callers = options.callers ?? createCallerResolver(undefined, scope);\n const pruner = sync && database ? createPruner(database) : undefined;\n let prepared: Promise<void> | undefined;\n const ready =\n options.ready ??\n (() => {\n prepared ??=\n sync && database\n ? ensureSyncTables(database.read() as SyncDatabase)\n : Promise.resolve();\n return prepared;\n });\n\n const cursorBefore = async (\n name: string,\n caller: Caller,\n ): Promise<string | undefined> => {\n const action = actions.get(name);\n if (!sync || !database || !action?.reads || isMutating(action)) return undefined;\n const streams =\n caller.audience === caller.scope ? [caller.scope] : [caller.scope, caller.audience];\n return encodeCursor(await readSyncCursor(database.read() as SyncDatabase, streams));\n };\n\n const invoke = async (\n name: string,\n input: unknown,\n request: Request | undefined,\n caller: Caller,\n source: ActionSource,\n runOptions: RunOptions,\n trace: Trace,\n ): Promise<ActionRun> => {\n if (runOptions.surface) requireSurface(caller, runOptions.surface, callers.enforcing);\n await admit?.(caller);\n const client = caller.clientId;\n onClient?.(client, caller.audience);\n const signal = runOptions.signal ?? request?.signal;\n const resolvedScope = caller.scope;\n const cursor = runOptions.cursor ? await cursorBefore(name, caller) : undefined;\n const result = await invokeAction(\n actions,\n name,\n input,\n { scope: resolvedScope, source, principal: caller.principal, signal, request },\n {\n data: database,\n enforcing: callers.enforcing,\n onRun: () => {\n trace.stage = \"ran\";\n },\n confirm: confirm\n ? confirmationGate({\n confirm,\n answered: (answer) => {\n trace.confirmation = answer;\n },\n scope: caller.audience,\n clientId: client,\n origin: runOptions.origin,\n signal,\n })\n : undefined,\n onWrite:\n sync && database\n ? async ({ action, tags, tx }) => {\n if (tags.length === 0) return;\n await recordSyncEvent(tx as SyncDatabase, {\n scope: action.notifies === \"user\" ? caller.audience : resolvedScope,\n tags,\n source: client,\n });\n }\n : undefined,\n },\n );\n if (result.writes.length > 0) pruner?.touch();\n return cursor === undefined ? result : { ...result, cursor };\n };\n\n return {\n ready,\n async run(name, input, request, declared, runOptions = {}) {\n await ready();\n const caller = request ? await callers(request) : INTERNAL_CALLER;\n const source = sourceFor(declared, caller, callers.enforcing);\n const trace: Trace = { stage: \"refused\", confirmation: undefined };\n if (!audit) return invoke(name, input, request, caller, source, runOptions, trace);\n const at = Date.now();\n const record = async (outcome: ActionAudit[\"outcome\"]) => {\n try {\n audit({\n at,\n action: name,\n principal: caller.principal,\n source,\n scope: caller.scope,\n inputDigest: await digest(input),\n stage: trace.stage,\n outcome,\n ...(trace.confirmation ? { confirmation: trace.confirmation } : {}),\n });\n } catch (error) {\n console.error(\"[parlor] audit failed:\", error);\n }\n };\n try {\n const result = await invoke(\n name,\n input,\n request,\n caller,\n source,\n runOptions,\n trace,\n );\n await record(\"ok\");\n return result;\n } catch (error) {\n await record(outcomeOf(error));\n throw error;\n }\n },\n };\n}\n\ninterface Trace {\n stage: ActionAudit[\"stage\"];\n confirmation: ConfirmationAnswer | undefined;\n}\n\nfunction outcomeOf(error: unknown): ActionAudit[\"outcome\"] {\n if (error instanceof ActionError) return error.code;\n if (error instanceof AuthFailure) return \"unauthenticated\";\n if (error instanceof Error && error.name === \"AbortError\") return \"cancelled\";\n return \"internal\";\n}\n\nasync function digest(input: unknown): Promise<string> {\n const bytes = new TextEncoder().encode(JSON.stringify(input ?? null));\n const hash = await crypto.subtle.digest(\"SHA-256\", bytes);\n return [...new Uint8Array(hash)].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"]}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AuthenticatedPrincipal, DeviceId, GrantId, HostPrincipal, Principal, ThreadId, TurnId, UserId } from "@parlor/core";
|
|
2
|
+
import type { ServerPlugin } from "./plugin.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where identity comes from. The server calls `authenticate` once per request and hands
|
|
5
|
+
* the principal to actions, MCP, sync and agent UI. It installs like any plugin, first.
|
|
6
|
+
*/
|
|
7
|
+
export interface AuthProvider extends ServerPlugin {
|
|
8
|
+
/**
|
|
9
|
+
* When true, an anonymous caller is refused by non-public actions, by MCP (with
|
|
10
|
+
* `challenge`) and by `/ui`, and `x-parlor-source` no longer decides an action's source.
|
|
11
|
+
*/
|
|
12
|
+
readonly enforcing: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Missing credentials resolve to `anonymous`. Present but invalid, expired or revoked
|
|
15
|
+
* credentials throw `AuthFailure`, which every surface answers with 401.
|
|
16
|
+
*/
|
|
17
|
+
authenticate(request: Request): Promise<Principal>;
|
|
18
|
+
/** The 401 an MCP client reads to discover the authorization server (RFC 9728). */
|
|
19
|
+
challenge(request: Request): Response;
|
|
20
|
+
/**
|
|
21
|
+
* Aborts when the session or grant behind the principal is revoked. Held connections
|
|
22
|
+
* (sync streams) close on it.
|
|
23
|
+
*/
|
|
24
|
+
revoked(principal: AuthenticatedPrincipal): AbortSignal;
|
|
25
|
+
/**
|
|
26
|
+
* Mints the turn token an inline agent's MCP calls carry while one chat turn runs,
|
|
27
|
+
* bound to the host's grant. Optional so providers written before chat still compile;
|
|
28
|
+
* `chat()` refuses to start on a provider without it.
|
|
29
|
+
*/
|
|
30
|
+
delegate?(host: HostPrincipal, turn: TurnDelegation): Promise<Delegation>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the host grant still holds. Lists of computers drop a grant that answers
|
|
33
|
+
* false at once, without waiting for the host to notice. Absent means every grant holds.
|
|
34
|
+
*/
|
|
35
|
+
hostActive?(grantId: GrantId): Promise<boolean>;
|
|
36
|
+
/** A device's public keys as the account registered them, for prompt and answer signatures. */
|
|
37
|
+
device?(userId: UserId, deviceId: DeviceId): Promise<DeviceKeys | undefined>;
|
|
38
|
+
/** Who the agent works for, for the turn's context block. */
|
|
39
|
+
account?(userId: UserId): Promise<AccountProfile | undefined>;
|
|
40
|
+
}
|
|
41
|
+
/** Announced when a computer's grant is added or removed; whatever lists computers reads it. */
|
|
42
|
+
export declare const HOST_GRANTS_TAG = "parlor_auth_hosts";
|
|
43
|
+
export interface TurnDelegation {
|
|
44
|
+
readonly threadId: ThreadId;
|
|
45
|
+
readonly turnId: TurnId;
|
|
46
|
+
readonly clientId: string;
|
|
47
|
+
readonly agent: string;
|
|
48
|
+
readonly ttlMs: number;
|
|
49
|
+
}
|
|
50
|
+
export interface Delegation {
|
|
51
|
+
readonly token: string;
|
|
52
|
+
readonly expiresAt: number;
|
|
53
|
+
}
|
|
54
|
+
export interface DeviceKeys {
|
|
55
|
+
readonly id: DeviceId;
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly platform: string;
|
|
58
|
+
readonly signingKey: JsonWebKey;
|
|
59
|
+
readonly presenceKey?: PresenceKey | undefined;
|
|
60
|
+
/** Linked devices' signed word that this one is the user's; each computer checks them itself. */
|
|
61
|
+
readonly vouches?: readonly DeviceVouch[] | undefined;
|
|
62
|
+
}
|
|
63
|
+
/** A `VouchStatement` as the voucher signed it, with its presence key. */
|
|
64
|
+
export interface DeviceVouch {
|
|
65
|
+
readonly voucher: DeviceId;
|
|
66
|
+
readonly commitment: string;
|
|
67
|
+
readonly at: number;
|
|
68
|
+
readonly signature: {
|
|
69
|
+
readonly keyId: string;
|
|
70
|
+
readonly presence: "none" | "user";
|
|
71
|
+
readonly format: "es256-raw" | "webauthn";
|
|
72
|
+
readonly signature: string;
|
|
73
|
+
readonly authenticatorData?: string | undefined;
|
|
74
|
+
readonly clientDataJSON?: string | undefined;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export type PresenceKey = {
|
|
78
|
+
readonly format: "webauthn";
|
|
79
|
+
readonly credentialId: string;
|
|
80
|
+
readonly rpId: string;
|
|
81
|
+
readonly publicKey: JsonWebKey;
|
|
82
|
+
} | {
|
|
83
|
+
readonly format: "es256-raw";
|
|
84
|
+
readonly keyId: string;
|
|
85
|
+
readonly publicKey: JsonWebKey;
|
|
86
|
+
};
|
|
87
|
+
export interface AccountProfile {
|
|
88
|
+
readonly name: string;
|
|
89
|
+
readonly email?: string | undefined;
|
|
90
|
+
}
|
|
91
|
+
export type AuthFailureReason = "invalid" | "expired" | "revoked";
|
|
92
|
+
export declare class AuthFailure extends Error {
|
|
93
|
+
readonly reason: AuthFailureReason;
|
|
94
|
+
readonly name = "AuthFailure";
|
|
95
|
+
constructor(reason: AuthFailureReason, message: string);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Announced when a computer's grant is added or removed; whatever lists computers reads it. */
|
|
2
|
+
export const HOST_GRANTS_TAG = "parlor_auth_hosts";
|
|
3
|
+
export class AuthFailure extends Error {
|
|
4
|
+
reason;
|
|
5
|
+
name = "AuthFailure";
|
|
6
|
+
constructor(reason, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAoDA,gGAAgG;AAChG,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AA4DnD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAGzB,MAAM;IAFC,IAAI,GAAG,aAAa,CAAC;IACvC,YACW,MAAyB,EAClC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;sBAHN,MAAM;IAIjB,CAAC;CACF","sourcesContent":["import type {\n AuthenticatedPrincipal,\n DeviceId,\n GrantId,\n HostPrincipal,\n Principal,\n ThreadId,\n TurnId,\n UserId,\n} from \"@parlor/core\";\n\nimport type { ServerPlugin } from \"./plugin.js\";\n\n/**\n * Where identity comes from. The server calls `authenticate` once per request and hands\n * the principal to actions, MCP, sync and agent UI. It installs like any plugin, first.\n */\nexport interface AuthProvider extends ServerPlugin {\n /**\n * When true, an anonymous caller is refused by non-public actions, by MCP (with\n * `challenge`) and by `/ui`, and `x-parlor-source` no longer decides an action's source.\n */\n readonly enforcing: boolean;\n /**\n * Missing credentials resolve to `anonymous`. Present but invalid, expired or revoked\n * credentials throw `AuthFailure`, which every surface answers with 401.\n */\n authenticate(request: Request): Promise<Principal>;\n /** The 401 an MCP client reads to discover the authorization server (RFC 9728). */\n challenge(request: Request): Response;\n /**\n * Aborts when the session or grant behind the principal is revoked. Held connections\n * (sync streams) close on it.\n */\n revoked(principal: AuthenticatedPrincipal): AbortSignal;\n /**\n * Mints the turn token an inline agent's MCP calls carry while one chat turn runs,\n * bound to the host's grant. Optional so providers written before chat still compile;\n * `chat()` refuses to start on a provider without it.\n */\n delegate?(host: HostPrincipal, turn: TurnDelegation): Promise<Delegation>;\n /**\n * Whether the host grant still holds. Lists of computers drop a grant that answers\n * false at once, without waiting for the host to notice. Absent means every grant holds.\n */\n hostActive?(grantId: GrantId): Promise<boolean>;\n /** A device's public keys as the account registered them, for prompt and answer signatures. */\n device?(userId: UserId, deviceId: DeviceId): Promise<DeviceKeys | undefined>;\n /** Who the agent works for, for the turn's context block. */\n account?(userId: UserId): Promise<AccountProfile | undefined>;\n}\n\n/** Announced when a computer's grant is added or removed; whatever lists computers reads it. */\nexport const HOST_GRANTS_TAG = \"parlor_auth_hosts\";\n\nexport interface TurnDelegation {\n readonly threadId: ThreadId;\n readonly turnId: TurnId;\n readonly clientId: string;\n readonly agent: string;\n readonly ttlMs: number;\n}\n\nexport interface Delegation {\n readonly token: string;\n readonly expiresAt: number;\n}\n\nexport interface DeviceKeys {\n readonly id: DeviceId;\n readonly name: string;\n readonly platform: string;\n readonly signingKey: JsonWebKey;\n readonly presenceKey?: PresenceKey | undefined;\n /** Linked devices' signed word that this one is the user's; each computer checks them itself. */\n readonly vouches?: readonly DeviceVouch[] | undefined;\n}\n\n/** A `VouchStatement` as the voucher signed it, with its presence key. */\nexport interface DeviceVouch {\n readonly voucher: DeviceId;\n readonly commitment: string;\n readonly at: number;\n readonly signature: {\n readonly keyId: string;\n readonly presence: \"none\" | \"user\";\n readonly format: \"es256-raw\" | \"webauthn\";\n readonly signature: string;\n readonly authenticatorData?: string | undefined;\n readonly clientDataJSON?: string | undefined;\n };\n}\n\nexport type PresenceKey =\n | {\n readonly format: \"webauthn\";\n readonly credentialId: string;\n readonly rpId: string;\n readonly publicKey: JsonWebKey;\n }\n | {\n readonly format: \"es256-raw\";\n readonly keyId: string;\n readonly publicKey: JsonWebKey;\n };\n\nexport interface AccountProfile {\n readonly name: string;\n readonly email?: string | undefined;\n}\n\nexport type AuthFailureReason = \"invalid\" | \"expired\" | \"revoked\";\n\nexport class AuthFailure extends Error {\n override readonly name = \"AuthFailure\";\n constructor(\n readonly reason: AuthFailureReason,\n message: string,\n ) {\n super(message);\n }\n}\n"]}
|
package/dist/body.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request body as text, or undefined once it passes `limitBytes`. A declared
|
|
3
|
+
* `Content-Length` over the limit is refused unread; otherwise the stream is read chunk by
|
|
4
|
+
* chunk and cancelled the moment the bytes read pass the limit.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readBoundedText(request: Request, limitBytes: number): Promise<string | undefined>;
|
|
7
|
+
//# sourceMappingURL=body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB7B"}
|
package/dist/body.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request body as text, or undefined once it passes `limitBytes`. A declared
|
|
3
|
+
* `Content-Length` over the limit is refused unread; otherwise the stream is read chunk by
|
|
4
|
+
* chunk and cancelled the moment the bytes read pass the limit.
|
|
5
|
+
*/
|
|
6
|
+
export async function readBoundedText(request, limitBytes) {
|
|
7
|
+
const declared = Number(request.headers.get("content-length"));
|
|
8
|
+
if (declared > limitBytes)
|
|
9
|
+
return undefined;
|
|
10
|
+
const reader = request.body?.getReader();
|
|
11
|
+
if (!reader)
|
|
12
|
+
return "";
|
|
13
|
+
const decoder = new TextDecoder();
|
|
14
|
+
let text = "";
|
|
15
|
+
let size = 0;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const { done, value } = await reader.read();
|
|
18
|
+
if (done)
|
|
19
|
+
break;
|
|
20
|
+
size += value.byteLength;
|
|
21
|
+
if (size > limitBytes) {
|
|
22
|
+
await reader.cancel();
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
text += decoder.decode(value, { stream: true });
|
|
26
|
+
}
|
|
27
|
+
return text + decoder.decode();
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=body.js.map
|
package/dist/body.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,UAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC/D,IAAI,QAAQ,GAAG,UAAU;QAAE,OAAO,SAAS,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IACzC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,SAAS,CAAC;QACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,MAAM;QAChB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;QACzB,IAAI,IAAI,GAAG,UAAU,EAAE,CAAC;YACtB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;AACjC,CAAC","sourcesContent":["/**\n * The request body as text, or undefined once it passes `limitBytes`. A declared\n * `Content-Length` over the limit is refused unread; otherwise the stream is read chunk by\n * chunk and cancelled the moment the bytes read pass the limit.\n */\nexport async function readBoundedText(\n request: Request,\n limitBytes: number,\n): Promise<string | undefined> {\n const declared = Number(request.headers.get(\"content-length\"));\n if (declared > limitBytes) return undefined;\n const reader = request.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n let text = \"\";\n let size = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > limitBytes) {\n await reader.cancel();\n return undefined;\n }\n text += decoder.decode(value, { stream: true });\n }\n return text + decoder.decode();\n}\n"]}
|
package/dist/caller.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ActionSource, type Principal } from "@parlor/core";
|
|
2
|
+
import type { AuthProvider } from "./auth.js";
|
|
3
|
+
import type { ScopeResolver } from "./scope.js";
|
|
4
|
+
export interface Caller {
|
|
5
|
+
readonly principal: Principal;
|
|
6
|
+
readonly scope: string;
|
|
7
|
+
/** Scope plus user: whose screens this caller may see and drive (`audienceOf`). */
|
|
8
|
+
readonly audience: string;
|
|
9
|
+
/** The tab or app the request came from: a routing hint, never an identity. */
|
|
10
|
+
readonly clientId: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface CallerResolver {
|
|
13
|
+
(request: Request): Promise<Caller>;
|
|
14
|
+
readonly enforcing: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function sourceOf(request: Request): ActionSource;
|
|
17
|
+
export declare function clientOf(request: Request): string | undefined;
|
|
18
|
+
export declare function sourceFor(declared: ActionSource, caller: Caller, enforcing: boolean): ActionSource;
|
|
19
|
+
/** `prepare` runs before the provider sees a request, so its tables exist by then. */
|
|
20
|
+
export declare function createCallerResolver(auth: AuthProvider | undefined, scope: ScopeResolver, prepare?: () => Promise<void>): CallerResolver;
|
|
21
|
+
//# sourceMappingURL=caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../src/caller.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,SAAS,EAEf,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AASD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,YAAY,CAGvD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE7D;AAED,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,OAAO,GACjB,YAAY,CAId;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC5B,cAAc,CAwBhB"}
|