@every-app/sdk 0.1.14 → 0.3.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +608 -2
- package/dist/cloudflare/index.d.ts +21 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +21 -1
- package/dist/cloudflare/sentinel.d.ts +69 -0
- package/dist/cloudflare/sentinel.d.ts.map +1 -0
- package/dist/cloudflare/sentinel.js +50 -0
- package/dist/cloudflare/worker.d.ts +511 -0
- package/dist/cloudflare/worker.d.ts.map +1 -0
- package/dist/cloudflare/worker.js +738 -0
- package/dist/dev/index.d.ts +8 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +7 -0
- package/dist/dev/keypair.d.ts +24 -0
- package/dist/dev/keypair.d.ts.map +1 -0
- package/dist/dev/keypair.js +60 -0
- package/dist/internal/aiProviders.d.ts +67 -0
- package/dist/internal/aiProviders.d.ts.map +1 -0
- package/dist/internal/aiProviders.js +220 -0
- package/dist/internal/appSettings.d.ts +237 -0
- package/dist/internal/appSettings.d.ts.map +1 -0
- package/dist/internal/appSettings.js +374 -0
- package/dist/internal/capabilities.d.ts +1344 -0
- package/dist/internal/capabilities.d.ts.map +1 -0
- package/dist/internal/capabilities.js +1521 -0
- package/dist/internal/devProviders.d.ts +49 -0
- package/dist/internal/devProviders.d.ts.map +1 -0
- package/dist/internal/devProviders.js +62 -0
- package/dist/internal/email.d.ts +280 -0
- package/dist/internal/email.d.ts.map +1 -0
- package/dist/internal/email.js +613 -0
- package/dist/internal/events.d.ts +182 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +324 -0
- package/dist/internal/gatewaySettings.d.ts +82 -0
- package/dist/internal/gatewaySettings.d.ts.map +1 -0
- package/dist/internal/gatewaySettings.js +209 -0
- package/dist/internal/identity.d.ts +88 -0
- package/dist/internal/identity.d.ts.map +1 -0
- package/dist/internal/identity.js +231 -0
- package/dist/internal/index.d.ts +19 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +18 -0
- package/dist/internal/members.d.ts +35 -0
- package/dist/internal/members.d.ts.map +1 -0
- package/dist/internal/members.js +7 -0
- package/dist/internal/oauthClient.d.ts +63 -0
- package/dist/internal/oauthClient.d.ts.map +1 -0
- package/dist/internal/oauthClient.js +194 -0
- package/dist/internal/oauthScopes.d.ts +117 -0
- package/dist/internal/oauthScopes.d.ts.map +1 -0
- package/dist/internal/oauthScopes.js +416 -0
- package/dist/internal/organizationName.d.ts +18 -0
- package/dist/internal/organizationName.d.ts.map +1 -0
- package/dist/internal/organizationName.js +24 -0
- package/dist/internal/protocol.d.ts +195 -0
- package/dist/internal/protocol.d.ts.map +1 -0
- package/dist/internal/protocol.js +250 -0
- package/dist/internal/providerApps.d.ts +75 -0
- package/dist/internal/providerApps.d.ts.map +1 -0
- package/dist/internal/providerApps.js +59 -0
- package/dist/internal/providerRegistry.d.ts +147 -0
- package/dist/internal/providerRegistry.d.ts.map +1 -0
- package/dist/internal/providerRegistry.js +262 -0
- package/dist/internal/providerSpec.d.ts +55 -0
- package/dist/internal/providerSpec.d.ts.map +1 -0
- package/dist/internal/providerSpec.js +746 -0
- package/dist/internal/remoteMcp.d.ts +188 -0
- package/dist/internal/remoteMcp.d.ts.map +1 -0
- package/dist/internal/remoteMcp.js +543 -0
- package/dist/internal/tools.d.ts +115 -0
- package/dist/internal/tools.d.ts.map +1 -0
- package/dist/internal/tools.js +80 -0
- package/dist/server/attribution.d.ts +39 -0
- package/dist/server/attribution.d.ts.map +1 -0
- package/dist/server/attribution.js +67 -0
- package/dist/server/authorizeUrl.d.ts +43 -0
- package/dist/server/authorizeUrl.d.ts.map +1 -0
- package/dist/server/authorizeUrl.js +110 -0
- package/dist/server/emit.d.ts +63 -0
- package/dist/server/emit.d.ts.map +1 -0
- package/dist/server/emit.js +101 -0
- package/dist/server/everyApp.d.ts +81 -0
- package/dist/server/everyApp.d.ts.map +1 -0
- package/dist/server/everyApp.js +185 -0
- package/dist/server/gatewayFetch.d.ts +37 -0
- package/dist/server/gatewayFetch.d.ts.map +1 -0
- package/dist/server/gatewayFetch.js +115 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/integrationFetch.d.ts +131 -0
- package/dist/server/integrationFetch.d.ts.map +1 -0
- package/dist/server/integrationFetch.js +526 -0
- package/dist/server/listMembers.d.ts +32 -0
- package/dist/server/listMembers.d.ts.map +1 -0
- package/dist/server/listMembers.js +74 -0
- package/dist/server/nodeServer.d.ts +48 -0
- package/dist/server/nodeServer.d.ts.map +1 -0
- package/dist/server/nodeServer.js +45 -0
- package/dist/server/sendEmail.d.ts +39 -0
- package/dist/server/sendEmail.d.ts.map +1 -0
- package/dist/server/sendEmail.js +114 -0
- package/dist/server/tools.d.ts +184 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/dist/server/tools.js +347 -0
- package/dist/server/toolsEndpoint.d.ts +32 -0
- package/dist/server/toolsEndpoint.d.ts.map +1 -0
- package/dist/server/toolsEndpoint.js +231 -0
- package/package.json +21 -31
- package/dist/cloudflare/getLocalD1Url.d.ts +0 -2
- package/dist/cloudflare/getLocalD1Url.d.ts.map +0 -1
- package/dist/cloudflare/getLocalD1Url.js +0 -40
- package/dist/cloudflare/lazyInit.d.ts +0 -22
- package/dist/cloudflare/lazyInit.d.ts.map +0 -1
- package/dist/cloudflare/lazyInit.js +0 -65
- package/dist/cloudflare/server/gateway.d.ts +0 -29
- package/dist/cloudflare/server/gateway.d.ts.map +0 -1
- package/dist/cloudflare/server/gateway.js +0 -69
- package/dist/cloudflare/server/index.d.ts +0 -3
- package/dist/cloudflare/server/index.d.ts.map +0 -1
- package/dist/cloudflare/server/index.js +0 -2
- package/dist/core/authenticatedFetch.d.ts +0 -9
- package/dist/core/authenticatedFetch.d.ts.map +0 -1
- package/dist/core/authenticatedFetch.js +0 -31
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -2
- package/dist/core/sessionManager.d.ts +0 -58
- package/dist/core/sessionManager.d.ts.map +0 -1
- package/dist/core/sessionManager.js +0 -357
- package/dist/shared/bypassGatewayLocalOnly.d.ts +0 -15
- package/dist/shared/bypassGatewayLocalOnly.d.ts.map +0 -1
- package/dist/shared/bypassGatewayLocalOnly.js +0 -45
- package/dist/shared/parseMessagePayload.d.ts +0 -3
- package/dist/shared/parseMessagePayload.d.ts.map +0 -1
- package/dist/shared/parseMessagePayload.js +0 -18
- package/dist/tanstack/EmbeddedAppProvider.d.ts +0 -30
- package/dist/tanstack/EmbeddedAppProvider.d.ts.map +0 -1
- package/dist/tanstack/EmbeddedAppProvider.js +0 -57
- package/dist/tanstack/GatewayRequiredError.d.ts +0 -20
- package/dist/tanstack/GatewayRequiredError.d.ts.map +0 -1
- package/dist/tanstack/GatewayRequiredError.js +0 -91
- package/dist/tanstack/_internal/useEveryAppSession.d.ts +0 -18
- package/dist/tanstack/_internal/useEveryAppSession.d.ts.map +0 -1
- package/dist/tanstack/_internal/useEveryAppSession.js +0 -47
- package/dist/tanstack/index.d.ts +0 -4
- package/dist/tanstack/index.d.ts.map +0 -1
- package/dist/tanstack/index.js +0 -3
- package/dist/tanstack/server/authConfig.d.ts +0 -3
- package/dist/tanstack/server/authConfig.d.ts.map +0 -1
- package/dist/tanstack/server/authConfig.js +0 -13
- package/dist/tanstack/server/authenticateRequest.d.ts +0 -31
- package/dist/tanstack/server/authenticateRequest.d.ts.map +0 -1
- package/dist/tanstack/server/authenticateRequest.js +0 -154
- package/dist/tanstack/server/index.d.ts +0 -4
- package/dist/tanstack/server/index.d.ts.map +0 -1
- package/dist/tanstack/server/index.js +0 -2
- package/dist/tanstack/server/types.d.ts +0 -5
- package/dist/tanstack/server/types.d.ts.map +0 -1
- package/dist/tanstack/server/types.js +0 -1
- package/dist/tanstack/useEveryAppRouter.d.ts +0 -7
- package/dist/tanstack/useEveryAppRouter.d.ts.map +0 -1
- package/dist/tanstack/useEveryAppRouter.js +0 -66
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts +0 -2
- package/dist/tanstack/useSessionTokenClientMiddleware.d.ts.map +0 -1
- package/dist/tanstack/useSessionTokenClientMiddleware.js +0 -29
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX, OAUTH_TOKEN_BODY_LIMIT, allowedScopes, apiKeyHosts, authorizationFor, capabilitySettings, carriesToken, declaredScopeSets, describeTokenEndpoints, devProviderFile, grantedScopes, injectClientCredentials, integrationProxyPath, oauthClientCredentials, oauthClientOrigin, requestedScopes, requestedTokenScopes, resolveDevProvider, rewriteTokenRequestScope, scopeExcess, tokenEndpointFor, tokenGrantType, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
|
|
2
|
+
import { applyAttribution, checkAttribution, } from "./attribution.js";
|
|
3
|
+
import { BINDING_ORIGIN, streamingRequest, } from "./gatewayFetch.js";
|
|
4
|
+
/**
|
|
5
|
+
* A `fetch` for one of the gateway's capabilities. Call the provider's real
|
|
6
|
+
* URLs (`https://api.dataforseo.com/v3/…`); this sends them over the
|
|
7
|
+
* AppGateway binding, which checks the app's manifest lists the capability,
|
|
8
|
+
* injects the key the gateway holds and forwards the call. The app never
|
|
9
|
+
* sees the key. A refusal the gateway makes itself comes back as a response
|
|
10
|
+
* {@link gatewayRefusal} recognizes; anything else is the provider's answer.
|
|
11
|
+
*
|
|
12
|
+
* For an OAuth client (`linkedin`, `mastodon`, ...) it reaches the
|
|
13
|
+
* provider's token endpoints only, and the gateway puts its client on the
|
|
14
|
+
* request: send the code (or refresh token) without `client_id` or
|
|
15
|
+
* `client_secret`. Build the authorize URL with {@link authorizeUrl} from
|
|
16
|
+
* {@link integrationStatus}, and call the provider's API with the user's
|
|
17
|
+
* token directly.
|
|
18
|
+
*
|
|
19
|
+
* In local dev only, with no binding, the developer's own key or client
|
|
20
|
+
* from `.dev.vars` reaches the provider directly, under the gateway's names:
|
|
21
|
+
* a catalog provider's from the catalog, a provider registered on the
|
|
22
|
+
* gateway from its spec in `.everyapp/providers/<id>.json` (`everyapp
|
|
23
|
+
* provider show <id> --save` writes it), put on the call as the gateway's
|
|
24
|
+
* spec says.
|
|
25
|
+
*/
|
|
26
|
+
export function integrationFetch({ env, provider, for: target, }) {
|
|
27
|
+
const catalogEntry = fetchableCapability(provider);
|
|
28
|
+
checkAttribution(env, target, `integrationFetch("${provider}")`);
|
|
29
|
+
const values = env;
|
|
30
|
+
const binding = values["EVERY_APP_GATEWAY"];
|
|
31
|
+
if (binding && typeof binding.fetch !== "function") {
|
|
32
|
+
throw new ConfigurationError("EVERY_APP_GATEWAY is configured but is not a valid service binding.");
|
|
33
|
+
}
|
|
34
|
+
// An id outside the catalog: the gateway's to resolve over the binding,
|
|
35
|
+
// or in local dev, the registered row's spec `everyapp dev` hands over.
|
|
36
|
+
const integration = catalogEntry ??
|
|
37
|
+
(binding ? undefined : devRegisteredProvider(provider, values));
|
|
38
|
+
const local = binding || !integration ? null : localCredentials(integration, values);
|
|
39
|
+
if (!binding && !local && integration) {
|
|
40
|
+
throw new ConfigurationError(`${integration.label} is unavailable: deploy with the EVERY_APP_GATEWAY binding, or set ${capabilitySettings(integration).join(", ")} in .dev.vars for local development.`);
|
|
41
|
+
}
|
|
42
|
+
return async (input, init) => {
|
|
43
|
+
const outbound = new Request(input, init);
|
|
44
|
+
const upstream = new URL(outbound.url);
|
|
45
|
+
const headers = new Headers(outbound.headers);
|
|
46
|
+
headers.delete("authorization");
|
|
47
|
+
headers.delete("cookie");
|
|
48
|
+
if (!integration) {
|
|
49
|
+
// A gateway-registered provider: the gateway holds its spec, so the
|
|
50
|
+
// gateway checks the host; here only that it is https at all.
|
|
51
|
+
if (upstream.protocol !== "https:") {
|
|
52
|
+
throw new TypeError(`integrationFetch("${provider}") reaches https URLs only, not ${upstream.protocol}//${upstream.host}.`);
|
|
53
|
+
}
|
|
54
|
+
applyAttribution(headers, target);
|
|
55
|
+
return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
|
|
56
|
+
}
|
|
57
|
+
if (integration.mode === "oauth_client") {
|
|
58
|
+
return oauthClientCall({
|
|
59
|
+
provider: provider,
|
|
60
|
+
capability: integration,
|
|
61
|
+
outbound,
|
|
62
|
+
upstream,
|
|
63
|
+
headers,
|
|
64
|
+
values,
|
|
65
|
+
...(binding
|
|
66
|
+
? { binding, target }
|
|
67
|
+
: { client: local }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// In local dev a provider's host setting (`.dev.vars`) adds its host,
|
|
71
|
+
// as the gateway's setting does there.
|
|
72
|
+
const hosts = binding
|
|
73
|
+
? [...integration.hosts]
|
|
74
|
+
: apiKeyHosts(integration, values);
|
|
75
|
+
if (upstream.protocol !== "https:" || !hosts.includes(upstream.host)) {
|
|
76
|
+
throw new TypeError(`integrationFetch("${provider}") only reaches ${hosts.join(", ")}, not ${upstream.host}.`);
|
|
77
|
+
}
|
|
78
|
+
if (binding) {
|
|
79
|
+
applyAttribution(headers, target);
|
|
80
|
+
return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
|
|
81
|
+
}
|
|
82
|
+
applyAttribution(headers, undefined);
|
|
83
|
+
// The developer's own key, put where the provider's entry says, as the
|
|
84
|
+
// gateway would put its own.
|
|
85
|
+
const auth = local;
|
|
86
|
+
for (const [name, value] of Object.entries(auth.headers)) {
|
|
87
|
+
headers.set(name, value);
|
|
88
|
+
}
|
|
89
|
+
for (const [name, value] of Object.entries(auth.query)) {
|
|
90
|
+
upstream.searchParams.set(name, value);
|
|
91
|
+
}
|
|
92
|
+
return fetch(streamingRequest(upstream.toString(), outbound, headers));
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The developer's own key or client from `.dev.vars` (with EVERYAPP_DEV
|
|
97
|
+
* set), under the gateway's names; null until every setting it needs is.
|
|
98
|
+
*/
|
|
99
|
+
function localCredentials(integration, values) {
|
|
100
|
+
if (!values["EVERYAPP_DEV"])
|
|
101
|
+
return null;
|
|
102
|
+
if (integration.mode === "oauth_client") {
|
|
103
|
+
return oauthClientCredentials(integration, values);
|
|
104
|
+
}
|
|
105
|
+
return authorizationFor(integration, values);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A call to an OAuth client's token endpoint: over the binding, where the
|
|
109
|
+
* gateway puts its client on it, or in local dev straight to the provider
|
|
110
|
+
* with the developer's client put on it by the gateway's own rule.
|
|
111
|
+
*/
|
|
112
|
+
async function oauthClientCall({ provider, capability, outbound, upstream, headers, values, binding, target, client, }) {
|
|
113
|
+
// With the binding the gateway resolves the provider's origin (Mastodon's
|
|
114
|
+
// instance) and refuses any other, so any origin stands in for it here.
|
|
115
|
+
const settings = binding && capability.hostSetting
|
|
116
|
+
? { [capability.hostSetting]: upstream.origin }
|
|
117
|
+
: values;
|
|
118
|
+
const endpoint = tokenEndpointFor(capability, upstream, settings);
|
|
119
|
+
const method = outbound.method.toUpperCase();
|
|
120
|
+
if (!endpoint || !endpoint.methods.includes(method)) {
|
|
121
|
+
throw new TypeError(`integrationFetch("${provider}") only reaches ${capability.label}'s token endpoints (${describeTokenEndpoints(capability)}), not ${method} ${upstream.origin}${upstream.pathname}. Call its API with the user's token directly.`);
|
|
122
|
+
}
|
|
123
|
+
if (binding) {
|
|
124
|
+
applyAttribution(headers, target);
|
|
125
|
+
return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
|
|
126
|
+
}
|
|
127
|
+
applyAttribution(headers, undefined);
|
|
128
|
+
// The gateway's scope checks, in its order, answering its refusals: an
|
|
129
|
+
// app sees in `everyapp dev` what the gateway will say.
|
|
130
|
+
const policy = capability.scopePolicy;
|
|
131
|
+
const grant = policy
|
|
132
|
+
? devScopeSets(policy, provider, capability.label, values)
|
|
133
|
+
: null;
|
|
134
|
+
if (grant && "problem" in grant) {
|
|
135
|
+
return devRefusal(403, grant.code, grant.problem);
|
|
136
|
+
}
|
|
137
|
+
const sets = grant?.sets;
|
|
138
|
+
const body = method === "GET" ? null : await outbound.text();
|
|
139
|
+
if (body !== null &&
|
|
140
|
+
new TextEncoder().encode(body).byteLength > OAUTH_TOKEN_BODY_LIMIT) {
|
|
141
|
+
throw new TypeError(`A token request's body is at most ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB.`);
|
|
142
|
+
}
|
|
143
|
+
if (policy &&
|
|
144
|
+
["grant_type", "scope", "user_scope"].some((name) => upstream.searchParams.has(name))) {
|
|
145
|
+
return devRefusal(400, "integration_body_invalid", "Put grant_type, scope and user_scope in the token request's body, not its query string; the gateway decides on the body.");
|
|
146
|
+
}
|
|
147
|
+
let injected = injectClientCredentials({ url: upstream, headers, body }, endpoint, client);
|
|
148
|
+
if (!injected.ok)
|
|
149
|
+
throw new TypeError(injected.problem);
|
|
150
|
+
if (policy && sets) {
|
|
151
|
+
const refused = refuseTokenRequest(policy, capability, sets, injected);
|
|
152
|
+
if (refused)
|
|
153
|
+
return refused;
|
|
154
|
+
injected = {
|
|
155
|
+
ok: true,
|
|
156
|
+
...rewriteTokenRequestScope(policy, sets, injected),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const response = await fetch(new Request(injected.url, {
|
|
160
|
+
method,
|
|
161
|
+
headers: injected.headers,
|
|
162
|
+
body: injected.body,
|
|
163
|
+
signal: outbound.signal,
|
|
164
|
+
// As the gateway: the client never follows a redirect.
|
|
165
|
+
redirect: "manual",
|
|
166
|
+
}));
|
|
167
|
+
if (!policy || !sets || !response.ok)
|
|
168
|
+
return response;
|
|
169
|
+
return checkTokenResponse(policy, capability, sets, response);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The gateway's refusal shape (`{ error, code, message }`, the code in
|
|
173
|
+
* `x-everyapp-error`) from the local-dev twin, so {@link gatewayRefusal}
|
|
174
|
+
* reads it the same way.
|
|
175
|
+
*/
|
|
176
|
+
function devRefusal(status, code, message) {
|
|
177
|
+
const labels = {
|
|
178
|
+
400: "Bad Request",
|
|
179
|
+
403: "Forbidden",
|
|
180
|
+
502: "Bad Gateway",
|
|
181
|
+
};
|
|
182
|
+
return Response.json({ error: labels[status] ?? "Error", code, message }, { status, headers: { [GATEWAY_ERROR_HEADER]: code } });
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The scope sets the manifest declares for `id`, read from
|
|
186
|
+
* EVERYAPP_DEV_CAPABILITIES (the manifest's `capabilities` as JSON, which
|
|
187
|
+
* `everyAppWorker()` writes in dev), or the gateway's refusal, in its
|
|
188
|
+
* order: the client not listed for writing (`capability_not_requested`),
|
|
189
|
+
* then a void grant (`integration_scopes_undeclared`: the variable
|
|
190
|
+
* missing, the entry without usable `scopes`, or a set this registry
|
|
191
|
+
* doesn't offer). Fails closed like the gateway: a hand-rolled dev harness
|
|
192
|
+
* that passes no capabilities gets nothing.
|
|
193
|
+
*/
|
|
194
|
+
function devScopeSets(policy, id, label, values) {
|
|
195
|
+
const offered = Object.keys(policy.sets).join(", ");
|
|
196
|
+
const raw = values["EVERYAPP_DEV_CAPABILITIES"];
|
|
197
|
+
let capabilities;
|
|
198
|
+
try {
|
|
199
|
+
capabilities =
|
|
200
|
+
typeof raw === "string" ? jsonRecord(JSON.parse(raw)) : undefined;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
capabilities = undefined;
|
|
204
|
+
}
|
|
205
|
+
if (!capabilities) {
|
|
206
|
+
return {
|
|
207
|
+
code: "integration_scopes_undeclared",
|
|
208
|
+
problem: `${label}'s scope sets aren't known here: everyapp dev passes the manifest's capabilities to the Worker as EVERYAPP_DEV_CAPABILITIES when everyapp.config.ts builds it with everyAppWorker() from @every-app/sdk/cloudflare.`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const entry = Object.hasOwn(capabilities, id)
|
|
212
|
+
? jsonRecord(capabilities[id])
|
|
213
|
+
: undefined;
|
|
214
|
+
// The gateway's check 2: listing the client for writing is the grant.
|
|
215
|
+
if (entry?.["access"] !== "write") {
|
|
216
|
+
return {
|
|
217
|
+
code: "capability_not_requested",
|
|
218
|
+
problem: `This app doesn't list ${label} in its capabilities. Add capabilities: { ${JSON.stringify(id)}: { access: "write" } } to everyapp.config.ts and start everyapp dev again.`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const sets = declaredScopeSets(policy, entry);
|
|
222
|
+
if (sets)
|
|
223
|
+
return { sets };
|
|
224
|
+
const listed = entry["scopes"];
|
|
225
|
+
const unknown = Array.isArray(listed)
|
|
226
|
+
? listed.filter((name) => typeof name === "string" && !Object.hasOwn(policy.sets, name))
|
|
227
|
+
: [];
|
|
228
|
+
if (unknown.length > 0) {
|
|
229
|
+
return {
|
|
230
|
+
code: "integration_scopes_undeclared",
|
|
231
|
+
problem: `This app's ${label} entry names scope sets this SDK doesn't offer: ${unknown.join(", ")}. It offers: ${offered}. Fix everyapp.config.ts and start everyapp dev again, or update @every-app/sdk if it is older than the set.`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
code: "integration_scopes_undeclared",
|
|
236
|
+
problem: `This app lists ${label} without the scope sets it uses. Add scopes: [...] (one or more of: ${offered}) to its ${JSON.stringify(id)} entry in everyapp.config.ts and start everyapp dev again.`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function jsonRecord(value) {
|
|
240
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
241
|
+
? value
|
|
242
|
+
: undefined;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The gateway's checks on a token request before it goes out: the grant
|
|
246
|
+
* type must be one the policy completes, and a `scope` the body names must
|
|
247
|
+
* stay within the sets. Null when the request may go.
|
|
248
|
+
*/
|
|
249
|
+
function refuseTokenRequest(policy, capability, sets, request) {
|
|
250
|
+
const grantType = tokenGrantType(request);
|
|
251
|
+
if (!policy.grantTypes.includes(grantType)) {
|
|
252
|
+
const completes = policy.grantTypes.map((type) => type === null ? "a code exchange without grant_type" : type);
|
|
253
|
+
const asked = grantType === null
|
|
254
|
+
? "a request without grant_type"
|
|
255
|
+
: grantType === "other"
|
|
256
|
+
? "this request's grant_type"
|
|
257
|
+
: grantType;
|
|
258
|
+
return devRefusal(403, "integration_grant_not_allowed", `Through the gateway, ${capability.label} completes ${completes.join(" and ")} grants only; ${asked} isn't one of them. An app never gets app-level tokens with the operator's client.`);
|
|
259
|
+
}
|
|
260
|
+
const asked = requestedTokenScopes(policy, request);
|
|
261
|
+
const excess = asked ? scopeExcess(asked, allowedScopes(policy, sets)) : [];
|
|
262
|
+
if (excess.length > 0) {
|
|
263
|
+
return devRefusal(403, "integration_scope_not_allowed", `This app may ask ${capability.label} for ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}); the request also asks ${excess.join(", ")}. Build the authorize URL and the token request from integrationStatus().scopes.`);
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The gateway's check on a provider's 2xx answer: a token it carries must
|
|
269
|
+
* name scopes, and those must stay within the sets; on a refusal the
|
|
270
|
+
* answer is dropped, never forwarded, so no app holds a token outside its
|
|
271
|
+
* grant. An answer without a token (Slack's `ok: false`) is the app's to
|
|
272
|
+
* read. A non-2xx answer never reaches here.
|
|
273
|
+
*/
|
|
274
|
+
async function checkTokenResponse(policy, capability, sets, response) {
|
|
275
|
+
const invalid = () => devRefusal(502, "integration_response_invalid", `${capability.label} answered with something that isn't a token response (over ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB or not a JSON object); nothing was passed on.`);
|
|
276
|
+
const bytes = await response.arrayBuffer();
|
|
277
|
+
if (bytes.byteLength > OAUTH_TOKEN_BODY_LIMIT)
|
|
278
|
+
return invalid();
|
|
279
|
+
const text = new TextDecoder().decode(bytes);
|
|
280
|
+
let json;
|
|
281
|
+
try {
|
|
282
|
+
json = JSON.parse(text);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return invalid();
|
|
286
|
+
}
|
|
287
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
288
|
+
return invalid();
|
|
289
|
+
}
|
|
290
|
+
// The body was decoded on read: its length and encoding headers no
|
|
291
|
+
// longer describe it.
|
|
292
|
+
const headers = new Headers(response.headers);
|
|
293
|
+
for (const name of [
|
|
294
|
+
"set-cookie",
|
|
295
|
+
"content-length",
|
|
296
|
+
"content-encoding",
|
|
297
|
+
"transfer-encoding",
|
|
298
|
+
]) {
|
|
299
|
+
headers.delete(name);
|
|
300
|
+
}
|
|
301
|
+
const forward = () => new Response(text, {
|
|
302
|
+
status: response.status,
|
|
303
|
+
statusText: response.statusText,
|
|
304
|
+
headers,
|
|
305
|
+
});
|
|
306
|
+
if (!carriesToken(json))
|
|
307
|
+
return forward();
|
|
308
|
+
const granted = grantedScopes(policy, json);
|
|
309
|
+
if (!granted) {
|
|
310
|
+
return devRefusal(502, "integration_scope_unverified", `${capability.label} issued a token without saying which scopes it carries, so the gateway can't hold it to this app's scope sets; nothing was passed on. Ask the person to connect again.`);
|
|
311
|
+
}
|
|
312
|
+
const excess = scopeExcess(granted, allowedScopes(policy, sets));
|
|
313
|
+
if (excess.length > 0) {
|
|
314
|
+
const remedy = {
|
|
315
|
+
google: "Ask for only the scopes integrationStatus() returns, without include_granted_scopes, and connect again; if the app needs more, add the scope set to everyapp.config.ts and deploy.",
|
|
316
|
+
microsoft: "On a shared Microsoft client a token carries everything the person consented to this client: have them remove this app's consent at https://myapplications.microsoft.com and connect again, or add the scope set to everyapp.config.ts and deploy.",
|
|
317
|
+
slack: "Slack scopes add up per workspace installation: reinstall the app in the workspace with only the status's scopes, or add the scope set to everyapp.config.ts and deploy.",
|
|
318
|
+
}[policy.dialect];
|
|
319
|
+
return devRefusal(403, "integration_scope_exceeded", `${capability.label} granted scopes this app doesn't declare: ${excess.join(", ")}. It declares ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}). Nothing was passed on. ${remedy}`);
|
|
320
|
+
}
|
|
321
|
+
return forward();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* A provider outside the catalog in local dev (no binding): the gateway
|
|
325
|
+
* row's spec from EVERYAPP_DEV_PROVIDERS (`.everyapp/providers/<id>.json`,
|
|
326
|
+
* which `everyAppWorker()` reads in dev), or a ConfigurationError naming
|
|
327
|
+
* the file that doesn't parse, or the command that writes one.
|
|
328
|
+
*/
|
|
329
|
+
function devRegisteredProvider(provider, values) {
|
|
330
|
+
if (!values["EVERYAPP_DEV"]) {
|
|
331
|
+
throw new ConfigurationError(`"${provider}" isn't in the built-in provider catalog. A provider registered on the gateway (everyapp provider add) is reached through the EVERY_APP_GATEWAY binding, or in everyapp dev from ${devProviderFile(provider)}.`);
|
|
332
|
+
}
|
|
333
|
+
const resolved = resolveDevProvider(values, provider);
|
|
334
|
+
if (!resolved.found)
|
|
335
|
+
throw new ConfigurationError(resolved.problem);
|
|
336
|
+
return resolved.capability;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* The provider `integrationFetch()` calls: one whose key or OAuth client
|
|
340
|
+
* the gateway holds, or undefined for an id outside the built-in catalog
|
|
341
|
+
* (a provider registered on the gateway, which the binding resolves).
|
|
342
|
+
*/
|
|
343
|
+
function fetchableCapability(provider) {
|
|
344
|
+
const found = CATALOG.get(String(provider));
|
|
345
|
+
if (!found)
|
|
346
|
+
return undefined;
|
|
347
|
+
if (found.mode === "service") {
|
|
348
|
+
throw new ConfigurationError(`${String(provider)} is a gateway service: call its helper from @every-app/sdk/server (sendEmail(), listMembers()), not integrationFetch().`);
|
|
349
|
+
}
|
|
350
|
+
if (found.mode === "ai") {
|
|
351
|
+
throw new ConfigurationError(`${String(provider)} is an AI provider: list it under providers and call it with createGatewayFetch(), not integrationFetch().`);
|
|
352
|
+
}
|
|
353
|
+
return found;
|
|
354
|
+
}
|
|
355
|
+
function stringValue(value) {
|
|
356
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Whether the app can use an integration right now, without calling the
|
|
360
|
+
* provider: show "an admin needs to set up DataForSEO" (or "sending isn't
|
|
361
|
+
* set up") up front instead of failing mid-task. An OAuth client's status
|
|
362
|
+
* carries its public `clientId` for the authorize URL.
|
|
363
|
+
*/
|
|
364
|
+
export async function integrationStatus({ env, provider, }) {
|
|
365
|
+
const values = env;
|
|
366
|
+
const binding = values["EVERY_APP_GATEWAY"];
|
|
367
|
+
// A provider registered on the gateway is the gateway's to answer for;
|
|
368
|
+
// without one, the catalog, or in local dev the row's spec from
|
|
369
|
+
// `.everyapp/providers/<id>.json`.
|
|
370
|
+
const integration = binding && typeof binding.fetch === "function"
|
|
371
|
+
? CATALOG.get(String(provider))
|
|
372
|
+
: (CATALOG.get(String(provider)) ??
|
|
373
|
+
devRegisteredProvider(String(provider), values));
|
|
374
|
+
if (integration?.mode === "ai") {
|
|
375
|
+
throw new ConfigurationError(`${String(provider)} is an AI provider: it has no integration status (createGatewayFetch() answers ai_provider_not_configured when its key is unset).`);
|
|
376
|
+
}
|
|
377
|
+
const unavailable = () => (provider === "email"
|
|
378
|
+
? {
|
|
379
|
+
requested: false,
|
|
380
|
+
configured: false,
|
|
381
|
+
ready: false,
|
|
382
|
+
sender: null,
|
|
383
|
+
senders: [],
|
|
384
|
+
provider: null,
|
|
385
|
+
}
|
|
386
|
+
: {
|
|
387
|
+
requested: false,
|
|
388
|
+
configured: false,
|
|
389
|
+
ready: false,
|
|
390
|
+
});
|
|
391
|
+
if (binding && typeof binding.fetch === "function") {
|
|
392
|
+
const response = await binding.fetch(`${BINDING_ORIGIN}${INTEGRATION_PROXY_PREFIX}/${provider}`);
|
|
393
|
+
if (!response.ok)
|
|
394
|
+
return unavailable();
|
|
395
|
+
return (await response.json());
|
|
396
|
+
}
|
|
397
|
+
if (!integration)
|
|
398
|
+
return unavailable();
|
|
399
|
+
if (integration.mode === "service") {
|
|
400
|
+
if (provider === "email") {
|
|
401
|
+
return devEmailStatus(values);
|
|
402
|
+
}
|
|
403
|
+
// Members: listMembers() answers from the dev identity, nothing to set.
|
|
404
|
+
const dev = Boolean(values["EVERYAPP_DEV"]);
|
|
405
|
+
return {
|
|
406
|
+
requested: true,
|
|
407
|
+
configured: dev,
|
|
408
|
+
ready: dev,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (integration.mode === "oauth_client") {
|
|
412
|
+
return devOAuthClientStatus(provider, integration, values);
|
|
413
|
+
}
|
|
414
|
+
const configured = Boolean(values["EVERYAPP_DEV"] && authorizationFor(integration, values));
|
|
415
|
+
return {
|
|
416
|
+
requested: true,
|
|
417
|
+
configured,
|
|
418
|
+
ready: configured,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* An OAuth client in local dev, with no gateway: the developer's own, from
|
|
423
|
+
* `.dev.vars`, and for a client with scope sets the manifest's, from
|
|
424
|
+
* EVERYAPP_DEV_CAPABILITIES, shaped as the gateway's status is: an app
|
|
425
|
+
* that doesn't list the client is told nothing, and a void grant is not
|
|
426
|
+
* ready and hides the client id.
|
|
427
|
+
*/
|
|
428
|
+
function devOAuthClientStatus(id, capability, values) {
|
|
429
|
+
const client = values["EVERYAPP_DEV"]
|
|
430
|
+
? oauthClientCredentials(capability, values)
|
|
431
|
+
: null;
|
|
432
|
+
const origin = oauthClientOrigin(capability, values);
|
|
433
|
+
const policy = capability.scopePolicy;
|
|
434
|
+
const grant = policy
|
|
435
|
+
? devScopeSets(policy, id, capability.label, values)
|
|
436
|
+
: null;
|
|
437
|
+
if (grant && "problem" in grant) {
|
|
438
|
+
if (grant.code === "capability_not_requested") {
|
|
439
|
+
return { requested: false, configured: false, ready: false };
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
requested: true,
|
|
443
|
+
configured: Boolean(client),
|
|
444
|
+
ready: false,
|
|
445
|
+
scopeSets: [],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const asked = policy && grant ? requestedScopes(policy, grant.sets) : null;
|
|
449
|
+
return {
|
|
450
|
+
requested: true,
|
|
451
|
+
configured: Boolean(client),
|
|
452
|
+
ready: Boolean(client),
|
|
453
|
+
...(client ? { clientId: client.clientId } : {}),
|
|
454
|
+
...(client && origin ? { origin } : {}),
|
|
455
|
+
...(client && grant && asked
|
|
456
|
+
? {
|
|
457
|
+
scopeSets: grant.sets,
|
|
458
|
+
scopes: asked.scopes,
|
|
459
|
+
...(asked.userScopes.length > 0
|
|
460
|
+
? { userScopes: asked.userScopes }
|
|
461
|
+
: {}),
|
|
462
|
+
}
|
|
463
|
+
: {}),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Email in local dev, with no gateway: sends are printed unless `.dev.vars`
|
|
468
|
+
* sets EMAIL_FROM (and the REST settings), and the senders are the ones
|
|
469
|
+
* `.dev.vars` sets as EVERYAPP_EMAIL_SENDERS, standing in for the
|
|
470
|
+
* installation's (`everyapp email set --sender`); unset, none. The
|
|
471
|
+
* provider is the one the gateway would pick from the same settings:
|
|
472
|
+
* `cloudflare` for the REST pair, `resend` for RESEND_API_KEY, EMAIL_PROVIDER
|
|
473
|
+
* choosing when both are set.
|
|
474
|
+
*/
|
|
475
|
+
function devEmailStatus(values) {
|
|
476
|
+
const dev = Boolean(values["EVERYAPP_DEV"]);
|
|
477
|
+
const configured = dev && Boolean(stringValue(values["EMAIL_FROM"]));
|
|
478
|
+
const choice = chooseEmailProvider(values["EMAIL_PROVIDER"], (provider) => (provider === "cloudflare"
|
|
479
|
+
? stringValue(values["EMAIL_REST_API_TOKEN"]) &&
|
|
480
|
+
stringValue(values["CLOUDFLARE_ACCOUNT_ID"])
|
|
481
|
+
: stringValue(values["RESEND_API_KEY"]))
|
|
482
|
+
? "settings"
|
|
483
|
+
: null);
|
|
484
|
+
return {
|
|
485
|
+
requested: true,
|
|
486
|
+
configured,
|
|
487
|
+
ready: configured,
|
|
488
|
+
sender: configured
|
|
489
|
+
? parseEmailSender(stringValue(values["EMAIL_FROM"]), stringValue(values["EMAIL_FROM_NAME"]))
|
|
490
|
+
: null,
|
|
491
|
+
senders: dev ? parseEmailList(values[EMAIL_SENDERS_ENV]) : [],
|
|
492
|
+
provider: configured && choice.ok ? choice.provider : null,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
|
|
497
|
+
* when the gateway won't send (`email_sender_not_allowed`,
|
|
498
|
+
* `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
|
|
499
|
+
*/
|
|
500
|
+
export class GatewayRefusalError extends Error {
|
|
501
|
+
status;
|
|
502
|
+
code;
|
|
503
|
+
constructor({ status, code, message }) {
|
|
504
|
+
super(message);
|
|
505
|
+
this.name = "GatewayRefusalError";
|
|
506
|
+
this.status = status;
|
|
507
|
+
this.code = code;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The gateway's own refusal in `response`, or null when the response is the
|
|
512
|
+
* provider's answer (which may be an error of its own). Reads the body only
|
|
513
|
+
* for a refusal, so call it before reading the body yourself.
|
|
514
|
+
*/
|
|
515
|
+
export async function gatewayRefusal(response) {
|
|
516
|
+
const code = response.headers.get(GATEWAY_ERROR_HEADER);
|
|
517
|
+
if (!code)
|
|
518
|
+
return null;
|
|
519
|
+
const body = await response.json().catch(() => null);
|
|
520
|
+
const message = body &&
|
|
521
|
+
typeof body === "object" &&
|
|
522
|
+
typeof body.message === "string"
|
|
523
|
+
? body.message
|
|
524
|
+
: `The Every App gateway refused the call (${code}).`;
|
|
525
|
+
return { status: response.status, code, message };
|
|
526
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ListMembersResult } from "../internal/index.js";
|
|
2
|
+
import { type GatewayCallFor } from "./attribution.js";
|
|
3
|
+
export interface ListMembersOptions {
|
|
4
|
+
/** The Worker env: the EVERY_APP_GATEWAY binding when deployed; EVERYAPP_DEV in local dev. */
|
|
5
|
+
env: object;
|
|
6
|
+
/**
|
|
7
|
+
* Whom the list is for: the inbound request (its user), or
|
|
8
|
+
* `{ background: true, organizationId }` for work no person started (a
|
|
9
|
+
* cron sending reports). An organization's own app may omit it for
|
|
10
|
+
* background work; a shared app's Worker must pass one or the other
|
|
11
|
+
* ({@link AttributionError}).
|
|
12
|
+
*/
|
|
13
|
+
for?: GatewayCallFor;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The organization's members who can open the app now, through the
|
|
17
|
+
* gateway's `members` capability: people deactivated, removed from the
|
|
18
|
+
* organization or (for an organization's own app) not granted the app are
|
|
19
|
+
* left out. Read it when acting for members who aren't in a request, such
|
|
20
|
+
* as choosing whom a scheduled report goes to, instead of a copy the app
|
|
21
|
+
* kept. The app lists `capabilities: { members: { access: "read" } }`.
|
|
22
|
+
*
|
|
23
|
+
* Throws {@link GatewayRefusalError} when the gateway won't list
|
|
24
|
+
* (`capability_not_requested`, `integration_agent_not_allowed` for an
|
|
25
|
+
* agent's tool call, ...).
|
|
26
|
+
*
|
|
27
|
+
* In local dev (`everyapp dev`, no binding) there is no directory: a call
|
|
28
|
+
* for a request answers its signed-in user alone, and background work
|
|
29
|
+
* answers no one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function listMembers({ env, for: target, }: ListMembersOptions): Promise<ListMembersResult>;
|
|
32
|
+
//# sourceMappingURL=listMembers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listMembers.d.ts","sourceRoot":"","sources":["../../src/server/listMembers.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAK1B,MAAM,WAAW,kBAAkB;IACjC,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,EAChC,GAAG,EACH,GAAG,EAAE,MAAM,GACZ,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBjD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ConfigurationError, serviceRoutePath, } from "../internal/index.js";
|
|
2
|
+
import { applyAttribution, checkAttribution, isRequest, } from "./attribution.js";
|
|
3
|
+
import { getEveryAppUser } from "./everyApp.js";
|
|
4
|
+
import { BINDING_ORIGIN } from "./gatewayFetch.js";
|
|
5
|
+
import { GatewayRefusalError, gatewayRefusal } from "./integrationFetch.js";
|
|
6
|
+
/**
|
|
7
|
+
* The organization's members who can open the app now, through the
|
|
8
|
+
* gateway's `members` capability: people deactivated, removed from the
|
|
9
|
+
* organization or (for an organization's own app) not granted the app are
|
|
10
|
+
* left out. Read it when acting for members who aren't in a request, such
|
|
11
|
+
* as choosing whom a scheduled report goes to, instead of a copy the app
|
|
12
|
+
* kept. The app lists `capabilities: { members: { access: "read" } }`.
|
|
13
|
+
*
|
|
14
|
+
* Throws {@link GatewayRefusalError} when the gateway won't list
|
|
15
|
+
* (`capability_not_requested`, `integration_agent_not_allowed` for an
|
|
16
|
+
* agent's tool call, ...).
|
|
17
|
+
*
|
|
18
|
+
* In local dev (`everyapp dev`, no binding) there is no directory: a call
|
|
19
|
+
* for a request answers its signed-in user alone, and background work
|
|
20
|
+
* answers no one.
|
|
21
|
+
*/
|
|
22
|
+
export async function listMembers({ env, for: target, }) {
|
|
23
|
+
checkAttribution(env, target, "listMembers");
|
|
24
|
+
const values = env;
|
|
25
|
+
const binding = values["EVERY_APP_GATEWAY"];
|
|
26
|
+
if (binding) {
|
|
27
|
+
if (typeof binding.fetch !== "function") {
|
|
28
|
+
throw new ConfigurationError("EVERY_APP_GATEWAY is configured but is not a valid service binding.");
|
|
29
|
+
}
|
|
30
|
+
return listThroughGateway(binding, target);
|
|
31
|
+
}
|
|
32
|
+
if (!values["EVERYAPP_DEV"]) {
|
|
33
|
+
throw new ConfigurationError("Members are unavailable: deploy with the EVERY_APP_GATEWAY binding (everyapp deploy), or run under everyapp dev.");
|
|
34
|
+
}
|
|
35
|
+
return listInDev(env, target);
|
|
36
|
+
}
|
|
37
|
+
async function listThroughGateway(binding, target) {
|
|
38
|
+
const headers = new Headers();
|
|
39
|
+
applyAttribution(headers, target);
|
|
40
|
+
const response = await binding.fetch(new Request(`${BINDING_ORIGIN}${serviceRoutePath("members", "list")}`, {
|
|
41
|
+
method: "GET",
|
|
42
|
+
headers,
|
|
43
|
+
}));
|
|
44
|
+
const refusal = await gatewayRefusal(response);
|
|
45
|
+
if (refusal)
|
|
46
|
+
throw new GatewayRefusalError(refusal);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new GatewayRefusalError({
|
|
49
|
+
status: response.status,
|
|
50
|
+
code: "members_list_failed",
|
|
51
|
+
message: `The Every App gateway answered ${response.status} to the members list.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return (await response.json());
|
|
55
|
+
}
|
|
56
|
+
/** The dev gateway has one person: the request's, if the call is for one. */
|
|
57
|
+
async function listInDev(env, target) {
|
|
58
|
+
const user = target && isRequest(target) ? await getEveryAppUser(target, env) : null;
|
|
59
|
+
if (!user)
|
|
60
|
+
return { members: [] };
|
|
61
|
+
return {
|
|
62
|
+
members: [
|
|
63
|
+
{
|
|
64
|
+
id: user.id,
|
|
65
|
+
email: user.email,
|
|
66
|
+
name: "",
|
|
67
|
+
role: memberRole(user.orgRole),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function memberRole(role) {
|
|
73
|
+
return role === "owner" || role === "admin" ? role : "member";
|
|
74
|
+
}
|