@every-app/sdk 0.3.0-rc.3 → 0.3.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.d.ts +21 -4
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +8 -4
- package/dist/cloudflare/worker.d.ts +181 -6
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +194 -11
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/dev/keypair.d.ts +2 -0
- package/dist/dev/keypair.d.ts.map +1 -1
- package/dist/dev/keypair.js +2 -0
- package/dist/internal/capabilities.d.ts +1 -1
- package/dist/internal/catalog/index.d.ts +1 -1
- package/dist/internal/catalog/inference.d.ts +1 -1
- package/dist/internal/catalog/inference.js +3 -3
- package/dist/internal/events.d.ts +1 -1
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +23 -1
- package/dist/internal/identity.d.ts +29 -3
- package/dist/internal/identity.d.ts.map +1 -1
- package/dist/internal/identity.js +37 -9
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/members.d.ts +3 -2
- package/dist/internal/members.d.ts.map +1 -1
- package/dist/internal/protocol.d.ts +25 -1
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +21 -1
- package/dist/server/attribution.d.ts +9 -5
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +12 -4
- package/dist/server/context.d.ts +68 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +117 -0
- package/dist/server/emit.d.ts +1 -1
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +4 -4
- package/dist/server/everyApp.d.ts +27 -7
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +106 -5
- package/dist/server/fromMcpTools.d.ts +89 -0
- package/dist/server/fromMcpTools.d.ts.map +1 -0
- package/dist/server/fromMcpTools.js +192 -0
- package/dist/server/gatewayFetch.d.ts +8 -6
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +6 -3
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -1
- package/dist/server/integrationFetch.d.ts +17 -7
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +22 -6
- package/dist/server/listMembers.d.ts +1 -1
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +5 -8
- package/dist/server/sendEmail.d.ts +1 -1
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +4 -4
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +11 -5
- package/package.json +13 -14
- package/LICENSE +0 -21
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* survives verification produces a user.
|
|
10
10
|
*/
|
|
11
11
|
import { importSPKI, jwtVerify, decodeProtectedHeader, } from "jose";
|
|
12
|
-
import { APP_ID_ENV, DEV_ENV, DEV_ISSUER, IDENTITY_ALG, IDENTITY_DEV_KEY_ID, IDENTITY_HEADER, IDENTITY_KEY_ID, ISSUER_ENV, PUBLIC_HEADER, PUBLIC_KEYS_ENV, PUBLIC_MARKER_SUB, assertPublicMarkerClaims, assertSystemClaims, identityClaimsToEveryAppUser, ProtocolClaimsError, } from "./protocol.js";
|
|
13
|
-
export { APP_ID_ENV, DEV_ENV, DEV_ISSUER, IDENTITY_ALG, IDENTITY_DEV_KEY_ID, IDENTITY_HEADER, IDENTITY_KEY_ID, ISSUER_ENV, PUBLIC_HEADER, PUBLIC_KEYS_ENV, PUBLIC_MARKER_SUB, };
|
|
12
|
+
import { APP_ID_ENV, DEV_ENV, DEV_ISSUER, IDENTITY_ALG, IDENTITY_DEV_KEY_ID, IDENTITY_HEADER, IDENTITY_KEY_ID, ISSUER_ENV, ORG_ID_ENV, PUBLIC_HEADER, PUBLIC_KEYS_ENV, PUBLIC_MARKER_SUB, assertPublicMarkerClaims, assertSystemClaims, identityClaimsToEveryAppUser, ProtocolClaimsError, } from "./protocol.js";
|
|
13
|
+
export { APP_ID_ENV, DEV_ENV, DEV_ISSUER, IDENTITY_ALG, IDENTITY_DEV_KEY_ID, IDENTITY_HEADER, IDENTITY_KEY_ID, ISSUER_ENV, ORG_ID_ENV, PUBLIC_HEADER, PUBLIC_KEYS_ENV, PUBLIC_MARKER_SUB, };
|
|
14
14
|
export class IdentityError extends Error {
|
|
15
15
|
status = 401;
|
|
16
16
|
constructor(message) {
|
|
@@ -81,9 +81,19 @@ function assertDevIssuer(options) {
|
|
|
81
81
|
* claims).
|
|
82
82
|
*/
|
|
83
83
|
export async function verifyIdentityJwt(token, options) {
|
|
84
|
+
return (await verifyIdentity(token, options)).user;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* {@link verifyIdentityJwt}, keeping the token's expiry: the SDK's request
|
|
88
|
+
* context stops forwarding an identity shortly before it, and goes on as
|
|
89
|
+
* the organization's background work.
|
|
90
|
+
*/
|
|
91
|
+
export async function verifyIdentity(token, options) {
|
|
84
92
|
const payload = await verifyPinnedJwt(token, options, "user");
|
|
85
93
|
try {
|
|
86
|
-
|
|
94
|
+
const user = identityClaimsToEveryAppUser(payload);
|
|
95
|
+
// `exp` is a required claim (verifyPinnedJwt); jose checked it.
|
|
96
|
+
return { user, expiresAt: payload.exp * 1000 };
|
|
87
97
|
}
|
|
88
98
|
catch (error) {
|
|
89
99
|
if (error instanceof ProtocolClaimsError) {
|
|
@@ -92,6 +102,17 @@ export async function verifyIdentityJwt(token, options) {
|
|
|
92
102
|
throw error;
|
|
93
103
|
}
|
|
94
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The organization pin: a deployed Worker serves one organization
|
|
107
|
+
* (`EVERY_APP_ORG_ID`), and an identity for any other is refused, whatever
|
|
108
|
+
* its audience says. Throws {@link IdentityError} on a mismatch; a Worker
|
|
109
|
+
* with no pin (a shared app's) accepts every organization.
|
|
110
|
+
*/
|
|
111
|
+
export function assertOrganizationPin(user, organizationId) {
|
|
112
|
+
if (organizationId && user.orgId !== organizationId) {
|
|
113
|
+
throw new IdentityError(`identity is for organization "${user.orgId}", and this Worker serves organization "${organizationId}" (${ORG_ID_ENV})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
95
116
|
/**
|
|
96
117
|
* Verify the gateway's system token (the value of `x-everyapp-system`)
|
|
97
118
|
* fail-closed, for exactly one purpose. A user identity or public marker is
|
|
@@ -137,7 +158,7 @@ async function verifyPinnedJwt(token, options, expectedTyp) {
|
|
|
137
158
|
throw new IdentityError(`unexpected kid "${header.kid}"`);
|
|
138
159
|
}
|
|
139
160
|
if (expectedTyp !== "public" && !options.issuer) {
|
|
140
|
-
throw new IdentityError(`${ISSUER_ENV} not configured — deploy with the
|
|
161
|
+
throw new IdentityError(`${ISSUER_ENV} not configured — deploy with the EveryApp CLI or provide an explicit issuer.`);
|
|
141
162
|
}
|
|
142
163
|
const verifyOpts = {
|
|
143
164
|
algorithms: [IDENTITY_ALG],
|
|
@@ -217,15 +238,22 @@ export function resolveVerifyOptions(options) {
|
|
|
217
238
|
*/
|
|
218
239
|
export async function getIdentityFromRequest(request, options) {
|
|
219
240
|
const verifyOptions = resolveVerifyOptions(options);
|
|
220
|
-
const
|
|
241
|
+
const headers = isRequestLike(request) ? request.headers : request;
|
|
242
|
+
const publicMarker = headers.get(PUBLIC_HEADER);
|
|
221
243
|
if (publicMarker) {
|
|
222
244
|
await verifyPublicMarkerJwt(publicMarker, verifyOptions);
|
|
223
|
-
return { user: null, isPublic: true };
|
|
245
|
+
return { user: null, isPublic: true, expiresAt: null };
|
|
224
246
|
}
|
|
225
|
-
const token =
|
|
247
|
+
const token = headers.get(IDENTITY_HEADER);
|
|
226
248
|
if (!token) {
|
|
227
249
|
throw new IdentityError("request reached the app without an identity token (not proxied through the gateway?)");
|
|
228
250
|
}
|
|
229
|
-
const user = await
|
|
230
|
-
|
|
251
|
+
const { user, expiresAt } = await verifyIdentity(token, verifyOptions);
|
|
252
|
+
assertOrganizationPin(user, options.organizationId);
|
|
253
|
+
return { user, isPublic: false, expiresAt };
|
|
254
|
+
}
|
|
255
|
+
// Duck-typed: a Request from another realm (a test runner, a polyfill) still
|
|
256
|
+
// carries its headers the same way.
|
|
257
|
+
function isRequestLike(value) {
|
|
258
|
+
return "headers" in value && typeof value.headers?.get === "function";
|
|
231
259
|
}
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -20,5 +20,5 @@ export * from "./providerRegistry.js";
|
|
|
20
20
|
export * from "./remoteMcp.js";
|
|
21
21
|
export * from "./signInProviders.js";
|
|
22
22
|
export * from "./devProviders.js";
|
|
23
|
-
export { ConfigurationError, IdentityError, getIdentityFromRequest, parsePublicKeys, resolveVerifyOptions, verifyIdentityJwt, verifyPublicMarkerJwt, verifySystemJwt, type IdentityResult, type RequestIdentityOptions, type VerifyOptions, } from "./identity.js";
|
|
23
|
+
export { ConfigurationError, IdentityError, assertOrganizationPin, getIdentityFromRequest, parsePublicKeys, resolveVerifyOptions, verifyIdentity, verifyIdentityJwt, verifyPublicMarkerJwt, verifySystemJwt, type IdentityResult, type RequestIdentityOptions, type VerifiedIdentity, type VerifyOptions, } from "./identity.js";
|
|
24
24
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/internal/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/internal/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC"}
|
package/dist/internal/index.js
CHANGED
|
@@ -20,4 +20,4 @@ export * from "./providerRegistry.js";
|
|
|
20
20
|
export * from "./remoteMcp.js";
|
|
21
21
|
export * from "./signInProviders.js";
|
|
22
22
|
export * from "./devProviders.js";
|
|
23
|
-
export { ConfigurationError, IdentityError, getIdentityFromRequest, parsePublicKeys, resolveVerifyOptions, verifyIdentityJwt, verifyPublicMarkerJwt, verifySystemJwt, } from "./identity.js";
|
|
23
|
+
export { ConfigurationError, IdentityError, assertOrganizationPin, getIdentityFromRequest, parsePublicKeys, resolveVerifyOptions, verifyIdentity, verifyIdentityJwt, verifyPublicMarkerJwt, verifySystemJwt, } from "./identity.js";
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* gateway builds it, the SDK's `listMembers()` returns it, and its local-dev
|
|
5
5
|
* fallback answers in the same shape. Types only.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import type { OrgRole } from "./protocol.js";
|
|
8
|
+
/** A member's role in the organization: the identity token's {@link OrgRole}. */
|
|
9
|
+
export type OrganizationMemberRole = OrgRole;
|
|
9
10
|
/**
|
|
10
11
|
* One member of the organization who can open the app: not deactivated,
|
|
11
12
|
* and, for an organization's own app, granted it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/internal/members.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,
|
|
1
|
+
{"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../src/internal/members.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,iFAAiF;AACjF,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE7C;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,sBAAsB,CAAC;CAC9B;AAED,2BAA2B;AAC3B,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED,yEAAyE;AACzE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,SAAS,CAAC;IACtB,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,UAAU,EAAE,IAAI,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,OAAO,CAAC;CAChB"}
|
|
@@ -74,6 +74,18 @@ export declare const ISSUER_ENV = "EVERYAPP_IDENTITY_ISSUER";
|
|
|
74
74
|
export declare const SHARED_ENV = "EVERYAPP_SHARED";
|
|
75
75
|
/** When set to `1` or `true` in the worker env, the dev identity kid is accepted. */
|
|
76
76
|
export declare const DEV_ENV = "EVERYAPP_DEV";
|
|
77
|
+
/**
|
|
78
|
+
* The organization an app's Worker is pinned to (a secret the deploy sends;
|
|
79
|
+
* in development the stub gateway's organization). `everyApp()` refuses an
|
|
80
|
+
* identity for any other organization; absent on a shared app's Workers.
|
|
81
|
+
*/
|
|
82
|
+
export declare const ORG_ID_ENV = "EVERY_APP_ORG_ID";
|
|
83
|
+
/**
|
|
84
|
+
* The organization the dev gateway's seeded user belongs to. The same value
|
|
85
|
+
* as `@every-app/sdk/dev`'s, kept apart like {@link DEV_ISSUER} (the same
|
|
86
|
+
* test pins the two together).
|
|
87
|
+
*/
|
|
88
|
+
export declare const DEV_ORG_ID = "dev-org";
|
|
77
89
|
/** `sub` claim the gateway mints public-route markers with. */
|
|
78
90
|
export declare const PUBLIC_MARKER_SUB = "public";
|
|
79
91
|
/** `sub` claim of gateway system tokens. */
|
|
@@ -92,12 +104,24 @@ export interface IdentitySubject {
|
|
|
92
104
|
orgId: string;
|
|
93
105
|
orgRole: string;
|
|
94
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A member's role in the organization, as the identity token carries it.
|
|
109
|
+
* Owners and admins manage; anything the Gateway might mint that an app
|
|
110
|
+
* doesn't know is read as `member`.
|
|
111
|
+
*/
|
|
112
|
+
export type OrgRole = "owner" | "admin" | "member";
|
|
113
|
+
/** `role` as an {@link OrgRole}: `owner` and `admin` as they are, anything else `member`. */
|
|
114
|
+
export declare function toOrgRole(role: unknown): OrgRole;
|
|
115
|
+
/** Whether `user` manages the organization: an owner or an admin. */
|
|
116
|
+
export declare function isAdmin(user: {
|
|
117
|
+
orgRole: OrgRole | string;
|
|
118
|
+
}): boolean;
|
|
95
119
|
export interface EveryAppUser {
|
|
96
120
|
/** User id (the `sub` claim). */
|
|
97
121
|
id: string;
|
|
98
122
|
email: string;
|
|
99
123
|
orgId: string;
|
|
100
|
-
orgRole:
|
|
124
|
+
orgRole: OrgRole;
|
|
101
125
|
channel: IdentityChannel;
|
|
102
126
|
/** The acting principal (user, or `mcp:<client>` / agent). */
|
|
103
127
|
actor: IdentityActor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/internal/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,YAAY,UAAU,CAAC;AACpC,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAC3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,kCAAkC,CAAC;AAC1D,eAAO,MAAM,eAAe,wBAAwB,CAAC;AACrD,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD;;;;GAIG;AACH,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AACvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,mBAAmB,CAAC;AAC3C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,uBAAuB,CAAC;AACnD,mDAAmD;AACnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,cAAc,CAAC;AACxD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,8CAA8C;AAC9C,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAC5C;;;;;GAKG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,qEAAqE;AACrE,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,mFAAmF;AACnF,eAAO,MAAM,UAAU,6BAA6B,CAAC;AACrD;;;GAGG;AACH,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAC5C,qFAAqF;AACrF,eAAO,MAAM,OAAO,iBAAiB,CAAC;AACtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7D,2DAA2D;AAC3D,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE9C,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/internal/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -71,10 +71,30 @@ export const ISSUER_ENV = "EVERYAPP_IDENTITY_ISSUER";
|
|
|
71
71
|
export const SHARED_ENV = "EVERYAPP_SHARED";
|
|
72
72
|
/** When set to `1` or `true` in the worker env, the dev identity kid is accepted. */
|
|
73
73
|
export const DEV_ENV = "EVERYAPP_DEV";
|
|
74
|
+
/**
|
|
75
|
+
* The organization an app's Worker is pinned to (a secret the deploy sends;
|
|
76
|
+
* in development the stub gateway's organization). `everyApp()` refuses an
|
|
77
|
+
* identity for any other organization; absent on a shared app's Workers.
|
|
78
|
+
*/
|
|
79
|
+
export const ORG_ID_ENV = "EVERY_APP_ORG_ID";
|
|
80
|
+
/**
|
|
81
|
+
* The organization the dev gateway's seeded user belongs to. The same value
|
|
82
|
+
* as `@every-app/sdk/dev`'s, kept apart like {@link DEV_ISSUER} (the same
|
|
83
|
+
* test pins the two together).
|
|
84
|
+
*/
|
|
85
|
+
export const DEV_ORG_ID = "dev-org";
|
|
74
86
|
/** `sub` claim the gateway mints public-route markers with. */
|
|
75
87
|
export const PUBLIC_MARKER_SUB = "public";
|
|
76
88
|
/** `sub` claim of gateway system tokens. */
|
|
77
89
|
export const SYSTEM_TOKEN_SUB = "system";
|
|
90
|
+
/** `role` as an {@link OrgRole}: `owner` and `admin` as they are, anything else `member`. */
|
|
91
|
+
export function toOrgRole(role) {
|
|
92
|
+
return role === "owner" || role === "admin" ? role : "member";
|
|
93
|
+
}
|
|
94
|
+
/** Whether `user` manages the organization: an owner or an admin. */
|
|
95
|
+
export function isAdmin(user) {
|
|
96
|
+
return user.orgRole === "owner" || user.orgRole === "admin";
|
|
97
|
+
}
|
|
78
98
|
export class ProtocolClaimsError extends Error {
|
|
79
99
|
constructor(message) {
|
|
80
100
|
super(message);
|
|
@@ -131,7 +151,7 @@ export function identityClaimsToEveryAppUser(payload) {
|
|
|
131
151
|
id: sub,
|
|
132
152
|
email,
|
|
133
153
|
orgId,
|
|
134
|
-
orgRole,
|
|
154
|
+
orgRole: toOrgRole(orgRole),
|
|
135
155
|
channel: chan,
|
|
136
156
|
actor: { sub: act.sub ?? sub },
|
|
137
157
|
scopes,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whom a call through the gateway is for, which decides the organization
|
|
3
|
-
* it counts against:
|
|
3
|
+
* it counts against. Left out, it comes from the request context: the
|
|
4
|
+
* request `everyApp()` is serving (its person, while the identity is good),
|
|
5
|
+
* or the organization `withOrganization()` named. Given explicitly:
|
|
4
6
|
*
|
|
5
7
|
* - the inbound `Request`: its person, by the identity the gateway put on
|
|
6
8
|
* it (forwarded as is);
|
|
@@ -24,11 +26,13 @@ export declare class AttributionError extends Error {
|
|
|
24
26
|
constructor(message: string);
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* Whom the call is for, checked before any call is made: `target` as the
|
|
30
|
+
* app passed it, else the request context's answer
|
|
31
|
+
* ({@link attributionFromContext}; a `RequestContextError` outside every
|
|
32
|
+
* context). A shared app's Worker must end up with the request or an
|
|
33
|
+
* organization, and anything passed must be one of the two shapes.
|
|
30
34
|
*/
|
|
31
|
-
export declare function
|
|
35
|
+
export declare function attributionFor(env: object, target: GatewayCallFor | undefined, what: string): GatewayCallFor | undefined;
|
|
32
36
|
/**
|
|
33
37
|
* Put the call's attribution on its headers: the request's identity, or the
|
|
34
38
|
* background work's organization, and never one the app set itself.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/server/attribution.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/server/attribution.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GACtB,OAAO,GACP;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,0BAA0B;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,GACX,cAAc,GAAG,SAAS,CAI5B;AA8BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,GAAG,SAAS,GACjC,IAAI,CAUN;AASD,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAO1D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IDENTITY_HEADER, ORG_HEADER, SHARED_ENV } from "../internal/index.js";
|
|
2
|
+
import { attributionFromContext } from "./context.js";
|
|
2
3
|
/**
|
|
3
4
|
* A gateway call that doesn't say whom it is for where it must: from a
|
|
4
5
|
* shared app's Worker, with neither the request nor an organization.
|
|
@@ -11,11 +12,18 @@ export class AttributionError extends Error {
|
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Whom the call is for, checked before any call is made: `target` as the
|
|
16
|
+
* app passed it, else the request context's answer
|
|
17
|
+
* ({@link attributionFromContext}; a `RequestContextError` outside every
|
|
18
|
+
* context). A shared app's Worker must end up with the request or an
|
|
19
|
+
* organization, and anything passed must be one of the two shapes.
|
|
17
20
|
*/
|
|
18
|
-
export function
|
|
21
|
+
export function attributionFor(env, target, what) {
|
|
22
|
+
const resolved = target === undefined ? attributionFromContext(what) : target;
|
|
23
|
+
checkAttribution(env, resolved, what);
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
function checkAttribution(env, target, what) {
|
|
19
27
|
if (target !== undefined && !isRequest(target)) {
|
|
20
28
|
if (target === null || typeof target !== "object" || !target.background) {
|
|
21
29
|
throw new AttributionError(`${what}: \`for\` is the inbound Request or { background: true, organizationId }.`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { EveryAppUser } from "../internal/index.js";
|
|
2
|
+
/** What `everyApp()` keeps while it serves one request. */
|
|
3
|
+
export interface ServedRequest {
|
|
4
|
+
request: Request;
|
|
5
|
+
/** The verified user, or null on a public route. */
|
|
6
|
+
user: EveryAppUser | null;
|
|
7
|
+
/** When the forwarded identity expires (epoch ms); null without one. */
|
|
8
|
+
expiresAt: number | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A forwarded identity lives 120 seconds. One this close to its expiry could
|
|
12
|
+
* expire before the Gateway checks it (a 401 the call can't recover from),
|
|
13
|
+
* so work still running then goes on as the organization's background work.
|
|
14
|
+
*/
|
|
15
|
+
export declare const IDENTITY_EXPIRY_MARGIN_MS = 15000;
|
|
16
|
+
/**
|
|
17
|
+
* Code `request_context_missing`: `currentUser()` or a Gateway call with
|
|
18
|
+
* no `for`, outside any request `everyApp()` serves and outside
|
|
19
|
+
* `withOrganization()`. Work no request started names its organization.
|
|
20
|
+
*/
|
|
21
|
+
export declare class RequestContextError extends Error {
|
|
22
|
+
readonly code = "request_context_missing";
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Code `attribution_organization_mismatch`: `withOrganization()` named
|
|
27
|
+
* another organization than the request being served belongs to.
|
|
28
|
+
*/
|
|
29
|
+
export declare class OrganizationMismatchError extends Error {
|
|
30
|
+
readonly code = "attribution_organization_mismatch";
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
/** Serve `fn` for one request: `everyApp()` calls this; apps don't. */
|
|
34
|
+
export declare function serveRequest<T>(served: ServedRequest, fn: () => T): T;
|
|
35
|
+
/**
|
|
36
|
+
* Run `fn` as `organizationId`'s background work: every Gateway call made
|
|
37
|
+
* inside it (and in what it awaits or schedules) is that organization's,
|
|
38
|
+
* with no person behind it. For a Workflow step, a cron, a queue consumer or
|
|
39
|
+
* a Durable Object alarm, which no request started. An organization's own
|
|
40
|
+
* app names its own (`env.EVERY_APP_ORG_ID`); a shared app's Worker names
|
|
41
|
+
* the one the work is for. Inside a request another organization's person
|
|
42
|
+
* is driving, it throws {@link OrganizationMismatchError}; inside that
|
|
43
|
+
* organization's own request, the person stays attributed while their
|
|
44
|
+
* identity is good.
|
|
45
|
+
*/
|
|
46
|
+
export declare function withOrganization<T>(organizationId: string, fn: () => T): T;
|
|
47
|
+
/**
|
|
48
|
+
* The verified user of the request being served: an `EveryAppUser`, or
|
|
49
|
+
* `null` on a public route and inside `withOrganization()`'s background
|
|
50
|
+
* work. Throws {@link RequestContextError} outside both: there is no one
|
|
51
|
+
* to be, and a handler that isn't under `everyApp()` is a bug.
|
|
52
|
+
*/
|
|
53
|
+
export declare function currentUser(): EveryAppUser | null;
|
|
54
|
+
/**
|
|
55
|
+
* Whom a Gateway call made now is for, from the context:
|
|
56
|
+
*
|
|
57
|
+
* - the request being served, while its identity is good;
|
|
58
|
+
* - else that organization's background work (the identity is about to
|
|
59
|
+
* expire, or `withOrganization()` named it);
|
|
60
|
+
* - `undefined` on a public route of an organization's own app, which the
|
|
61
|
+
* Gateway attributes to the app's organization; and
|
|
62
|
+
* - a {@link RequestContextError} outside every context.
|
|
63
|
+
*/
|
|
64
|
+
export declare function attributionFromContext(what: string, now?: number): Request | {
|
|
65
|
+
background: true;
|
|
66
|
+
organizationId?: string;
|
|
67
|
+
} | undefined;
|
|
68
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAUD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,6BAA6B;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,uCAAuC;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAc1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,YAAY,GAAG,IAAI,CAQjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAmB,GACvB,OAAO,GAAG;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAoBrE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request context: whom the app is serving, kept for the whole of a
|
|
3
|
+
* request's work (every `await`, `ctx.waitUntil()`, a streaming body) in an
|
|
4
|
+
* `AsyncLocalStorage`, which every Every App Worker has (`nodejs_compat`).
|
|
5
|
+
*
|
|
6
|
+
* `everyApp()` opens it around the handler and around each tool call, with
|
|
7
|
+
* the verified user and the request the Gateway forwarded. Deeper in the
|
|
8
|
+
* app, `currentUser()` reads the user without the request, and every call
|
|
9
|
+
* back to the Gateway (`integrationFetch`, `ai`, `sendEmail`, `emit`, ...)
|
|
10
|
+
* is attributed to that person without a `for`.
|
|
11
|
+
*
|
|
12
|
+
* Work no request started (a cron, a Workflow step, a queue consumer, a
|
|
13
|
+
* Durable Object alarm) has no context of its own: it runs under
|
|
14
|
+
* `withOrganization(organizationId, fn)`, which makes every Gateway call
|
|
15
|
+
* inside it that organization's background work. A Gateway call with
|
|
16
|
+
* neither is refused before it is made ({@link RequestContextError}): the
|
|
17
|
+
* SDK never acts as nobody.
|
|
18
|
+
*/
|
|
19
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
|
+
const scopes = new AsyncLocalStorage();
|
|
21
|
+
/**
|
|
22
|
+
* A forwarded identity lives 120 seconds. One this close to its expiry could
|
|
23
|
+
* expire before the Gateway checks it (a 401 the call can't recover from),
|
|
24
|
+
* so work still running then goes on as the organization's background work.
|
|
25
|
+
*/
|
|
26
|
+
export const IDENTITY_EXPIRY_MARGIN_MS = 15_000;
|
|
27
|
+
/**
|
|
28
|
+
* Code `request_context_missing`: `currentUser()` or a Gateway call with
|
|
29
|
+
* no `for`, outside any request `everyApp()` serves and outside
|
|
30
|
+
* `withOrganization()`. Work no request started names its organization.
|
|
31
|
+
*/
|
|
32
|
+
export class RequestContextError extends Error {
|
|
33
|
+
code = "request_context_missing";
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "RequestContextError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Code `attribution_organization_mismatch`: `withOrganization()` named
|
|
41
|
+
* another organization than the request being served belongs to.
|
|
42
|
+
*/
|
|
43
|
+
export class OrganizationMismatchError extends Error {
|
|
44
|
+
code = "attribution_organization_mismatch";
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "OrganizationMismatchError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Serve `fn` for one request: `everyApp()` calls this; apps don't. */
|
|
51
|
+
export function serveRequest(served, fn) {
|
|
52
|
+
return scopes.run({ served }, fn);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Run `fn` as `organizationId`'s background work: every Gateway call made
|
|
56
|
+
* inside it (and in what it awaits or schedules) is that organization's,
|
|
57
|
+
* with no person behind it. For a Workflow step, a cron, a queue consumer or
|
|
58
|
+
* a Durable Object alarm, which no request started. An organization's own
|
|
59
|
+
* app names its own (`env.EVERY_APP_ORG_ID`); a shared app's Worker names
|
|
60
|
+
* the one the work is for. Inside a request another organization's person
|
|
61
|
+
* is driving, it throws {@link OrganizationMismatchError}; inside that
|
|
62
|
+
* organization's own request, the person stays attributed while their
|
|
63
|
+
* identity is good.
|
|
64
|
+
*/
|
|
65
|
+
export function withOrganization(organizationId, fn) {
|
|
66
|
+
if (typeof organizationId !== "string" || organizationId.trim() === "") {
|
|
67
|
+
throw new TypeError("withOrganization(organizationId, fn) needs a non-empty organization id (an organization's own app passes env.EVERY_APP_ORG_ID).");
|
|
68
|
+
}
|
|
69
|
+
const parent = scopes.getStore();
|
|
70
|
+
const served = parent?.served;
|
|
71
|
+
if (served?.user && served.user.orgId !== organizationId) {
|
|
72
|
+
throw new OrganizationMismatchError(`withOrganization("${organizationId}") was called while serving a request of organization "${served.user.orgId}".`);
|
|
73
|
+
}
|
|
74
|
+
return scopes.run({ ...parent, organizationId }, fn);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The verified user of the request being served: an `EveryAppUser`, or
|
|
78
|
+
* `null` on a public route and inside `withOrganization()`'s background
|
|
79
|
+
* work. Throws {@link RequestContextError} outside both: there is no one
|
|
80
|
+
* to be, and a handler that isn't under `everyApp()` is a bug.
|
|
81
|
+
*/
|
|
82
|
+
export function currentUser() {
|
|
83
|
+
const scope = scopes.getStore();
|
|
84
|
+
if (!scope) {
|
|
85
|
+
throw new RequestContextError("currentUser() was called outside a request everyApp() serves and outside withOrganization(): there is no user here.");
|
|
86
|
+
}
|
|
87
|
+
return scope.served?.user ?? null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Whom a Gateway call made now is for, from the context:
|
|
91
|
+
*
|
|
92
|
+
* - the request being served, while its identity is good;
|
|
93
|
+
* - else that organization's background work (the identity is about to
|
|
94
|
+
* expire, or `withOrganization()` named it);
|
|
95
|
+
* - `undefined` on a public route of an organization's own app, which the
|
|
96
|
+
* Gateway attributes to the app's organization; and
|
|
97
|
+
* - a {@link RequestContextError} outside every context.
|
|
98
|
+
*/
|
|
99
|
+
export function attributionFromContext(what, now = Date.now()) {
|
|
100
|
+
const scope = scopes.getStore();
|
|
101
|
+
if (!scope) {
|
|
102
|
+
throw new RequestContextError(`${what} was called with no \`for\`, outside a request everyApp() serves and outside withOrganization(): the call would be nobody's. Pass for: request, or run the work under withOrganization(organizationId, fn).`);
|
|
103
|
+
}
|
|
104
|
+
const served = scope.served;
|
|
105
|
+
if (served?.user) {
|
|
106
|
+
const fresh = served.expiresAt === null ||
|
|
107
|
+
now < served.expiresAt - IDENTITY_EXPIRY_MARGIN_MS;
|
|
108
|
+
// withOrganization() refused any other organization than the request's.
|
|
109
|
+
if (fresh)
|
|
110
|
+
return served.request;
|
|
111
|
+
return { background: true, organizationId: served.user.orgId };
|
|
112
|
+
}
|
|
113
|
+
if (scope.organizationId) {
|
|
114
|
+
return { background: true, organizationId: scope.organizationId };
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
package/dist/server/emit.d.ts
CHANGED
|
@@ -59,5 +59,5 @@ export interface EmitOptions {
|
|
|
59
59
|
* the gateway checks it, printed, and answered `{ id: "dev-…", type }`;
|
|
60
60
|
* nothing is delivered.
|
|
61
61
|
*/
|
|
62
|
-
export declare function emit({ env, for:
|
|
62
|
+
export declare function emit({ env, for: explicitTarget, event, data, }: EmitOptions): Promise<EmitEventResult>;
|
|
63
63
|
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/server/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EASL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAI1B,MAAM,WAAW,WAAW;IAC1B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,IAAI,CAAC,EACzB,GAAG,EACH,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/server/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EASL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAI1B,MAAM,WAAW,WAAW;IAC1B,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,IAAI,CAAC,EACzB,GAAG,EACH,GAAG,EAAE,cAAc,EACnB,KAAK,EACL,IAAI,GACL,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAoBxC"}
|
package/dist/server/emit.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* two can't drift.
|
|
10
10
|
*/
|
|
11
11
|
import { APP_EVENT_DATA_MAX_BYTES, APP_ID_ENV, ConfigurationError, EVENTS_EMIT_PATH, appEventNameProblem, appEventType, checkEventData, } from "../internal/index.js";
|
|
12
|
-
import { applyAttribution,
|
|
12
|
+
import { applyAttribution, attributionFor, } from "./attribution.js";
|
|
13
13
|
import { BINDING_ORIGIN } from "./gatewayFetch.js";
|
|
14
14
|
import { GatewayRefusalError, gatewayRefusal } from "./integrationFetch.js";
|
|
15
15
|
/**
|
|
@@ -36,8 +36,8 @@ import { GatewayRefusalError, gatewayRefusal } from "./integrationFetch.js";
|
|
|
36
36
|
* the gateway checks it, printed, and answered `{ id: "dev-…", type }`;
|
|
37
37
|
* nothing is delivered.
|
|
38
38
|
*/
|
|
39
|
-
export async function emit({ env, for:
|
|
40
|
-
|
|
39
|
+
export async function emit({ env, for: explicitTarget, event, data, }) {
|
|
40
|
+
const target = attributionFor(env, explicitTarget, "emit");
|
|
41
41
|
const values = env;
|
|
42
42
|
const binding = values["EVERY_APP_GATEWAY"];
|
|
43
43
|
if (binding) {
|
|
@@ -66,7 +66,7 @@ async function emitThroughGateway(binding, target, body) {
|
|
|
66
66
|
throw new GatewayRefusalError({
|
|
67
67
|
status: response.status,
|
|
68
68
|
code: "event_emit_failed",
|
|
69
|
-
message: `The
|
|
69
|
+
message: `The EveryApp gateway answered ${response.status} to the event.`,
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
return (await response.json());
|
|
@@ -14,11 +14,21 @@
|
|
|
14
14
|
* `cloudflare.config.ts` imports from the Worker in dev).
|
|
15
15
|
* It also answers the gateway's internal tools contract
|
|
16
16
|
* (`/__everyapp/tools/*`) for the app's `defineTools()` catalog.
|
|
17
|
+
*
|
|
18
|
+
* Around the handler and around each tool call it opens the request
|
|
19
|
+
* context (context.ts): `currentUser()` reads the user anywhere below, and
|
|
20
|
+
* every call back to the Gateway is attributed to the request without a
|
|
21
|
+
* `for`. A deployed Worker is pinned to its organization
|
|
22
|
+
* (`EVERY_APP_ORG_ID`), and an identity for any other is refused on both
|
|
23
|
+
* paths. The response leaves without `Set-Cookie`: the perimeter strips
|
|
24
|
+
* every `Cookie` before a request reaches the app, so a cookie the app's
|
|
25
|
+
* framework sets could never come back, and one named like the Gateway's
|
|
26
|
+
* session cookie would lock the person out of the app host.
|
|
17
27
|
*/
|
|
18
|
-
import { ConfigurationError, IdentityError, type EveryAppUser, type IdentityResult } from "../internal/index.js";
|
|
28
|
+
import { ConfigurationError, IdentityError, isAdmin, type EveryAppUser, type IdentityResult, type OrgRole } from "../internal/index.js";
|
|
19
29
|
import type { ToolSet } from "./tools.js";
|
|
20
|
-
export { ConfigurationError, IdentityError };
|
|
21
|
-
export type { EveryAppUser, IdentityResult };
|
|
30
|
+
export { ConfigurationError, IdentityError, isAdmin };
|
|
31
|
+
export type { EveryAppUser, IdentityResult, OrgRole };
|
|
22
32
|
/**
|
|
23
33
|
* The runtime's `ExecutionContext`, which `everyApp()` passes to the handler
|
|
24
34
|
* as the Worker received it, so it goes on to Hono's `app.fetch(request,
|
|
@@ -60,15 +70,25 @@ interface EveryAppExport<TEnv, TCtx extends ExecutionContextLike> {
|
|
|
60
70
|
}
|
|
61
71
|
/**
|
|
62
72
|
* Return the verified Every App user for this request, or null for a declared
|
|
63
|
-
* public request.
|
|
64
|
-
*
|
|
73
|
+
* public request. `request` is the inbound `Request` or just its `Headers`
|
|
74
|
+
* (a framework that hands out headers alone needs no made-up Request).
|
|
75
|
+
* Answers from the request context or the per-request cache first, then
|
|
76
|
+
* verifies the identity headers from the audience and public keys in env.
|
|
77
|
+
* Inside a handler `everyApp()` serves, `currentUser()` is the shorter way.
|
|
65
78
|
*/
|
|
66
|
-
export declare function getEveryAppUser(request: Request, env: object): Promise<EveryAppUser | null>;
|
|
79
|
+
export declare function getEveryAppUser(request: Request | Headers, env: object): Promise<EveryAppUser | null>;
|
|
67
80
|
/**
|
|
68
81
|
* Return the verified user, or throw the same 401 JSON Response shape as
|
|
69
82
|
* everyApp() when the request is public or unauthenticated.
|
|
70
83
|
*/
|
|
71
|
-
export declare function requireEveryAppUser(request: Request, env: object): Promise<EveryAppUser>;
|
|
84
|
+
export declare function requireEveryAppUser(request: Request | Headers, env: object): Promise<EveryAppUser>;
|
|
85
|
+
/**
|
|
86
|
+
* The user of the request being served (`currentUser()`), or throw the
|
|
87
|
+
* same 401 JSON Response as `requireEveryAppUser()` on a public route.
|
|
88
|
+
* Outside a request (`withOrganization()` work, a handler not under
|
|
89
|
+
* `everyApp()`) it throws `RequestContextError`, as `currentUser()` does.
|
|
90
|
+
*/
|
|
91
|
+
export declare function requireCurrentUser(): EveryAppUser;
|
|
72
92
|
export declare function hasScope(user: EveryAppUser, scope: string): boolean;
|
|
73
93
|
/** Wrap a Worker fetch handler with Every App identity verification. */
|
|
74
94
|
export declare function everyApp<TEnv = Record<string, unknown>, TCtx extends ExecutionContextLike = ExecutionContext>(handler: EveryAppHandler<TEnv, TCtx>, options?: EveryAppOptions): EveryAppExport<TEnv, TCtx>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"everyApp.d.ts","sourceRoot":"","sources":["../../src/server/everyApp.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"everyApp.d.ts","sourceRoot":"","sources":["../../src/server/everyApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAGL,kBAAkB,EAElB,aAAa,EAMb,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,OAAO,EAEb,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,CACzB,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,SAAS,oBAAoB,GAAG,gBAAgB,IAClD,CACF,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,YAAY,GAAG,IAAI,KACtB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAElC,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,SAAS,oBAAoB;IAC9D,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClE;AAuKD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,GAAG,OAAO,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAW9B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,GAAG,OAAO,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,YAAY,CAAC,CAevB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAMjD;AA0BD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnE;AAUD,wEAAwE;AACxE,wBAAgB,QAAQ,CACtB,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,SAAS,oBAAoB,GAAG,gBAAgB,EAEpD,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EACpC,OAAO,CAAC,EAAE,eAAe,GACxB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9B;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,SAAS,oBAAoB,GAAG,gBAAgB,EAEpD,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EACpC,QAAQ,EAAE,oBAAoB,GAAG,SAAS,EAC1C,OAAO,CAAC,EAAE,eAAe,GACxB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC"}
|