@decocms/runtime 1.0.0-alpha.5 → 1.0.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/config-schema.json +19 -8
- package/package.json +11 -17
- package/src/asset-server/index.test.ts +306 -0
- package/src/asset-server/index.ts +217 -34
- package/src/bindings/binder.ts +2 -5
- package/src/bindings/index.ts +0 -33
- package/src/bindings/language-model/utils.ts +0 -91
- package/src/bindings.ts +146 -139
- package/src/client.ts +1 -145
- package/src/cors.ts +140 -0
- package/src/events.ts +472 -0
- package/src/index.ts +206 -202
- package/src/mcp.ts +7 -166
- package/src/oauth.ts +495 -0
- package/src/proxy.ts +1 -208
- package/src/state.ts +3 -31
- package/src/tools.ts +645 -0
- package/src/wrangler.ts +6 -5
- package/tsconfig.json +1 -1
- package/src/admin.ts +0 -16
- package/src/auth.ts +0 -233
- package/src/bindings/deconfig/helpers.ts +0 -107
- package/src/bindings/deconfig/index.ts +0 -1
- package/src/bindings/deconfig/resources.ts +0 -689
- package/src/bindings/deconfig/types.ts +0 -106
- package/src/bindings/language-model/ai-sdk.ts +0 -90
- package/src/bindings/language-model/index.ts +0 -4
- package/src/bindings/resources/bindings.ts +0 -99
- package/src/bindings/resources/helpers.ts +0 -95
- package/src/bindings/resources/schemas.ts +0 -265
- package/src/bindings/views.ts +0 -14
- package/src/drizzle.ts +0 -201
- package/src/http-client-transport.ts +0 -1
- package/src/mastra.ts +0 -670
- package/src/mcp-client.ts +0 -139
- package/src/resources.ts +0 -168
- package/src/views.ts +0 -26
- package/src/well-known.ts +0 -20
package/src/auth.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { JWK, jwtVerify } from "jose";
|
|
2
|
-
import type { DefaultEnv } from "./index.ts";
|
|
3
|
-
|
|
4
|
-
const DECO_APP_AUTH_COOKIE_NAME = "deco_page_auth";
|
|
5
|
-
const MAX_COOKIE_SIZE = 4000; // Leave some buffer below the 4096 limit
|
|
6
|
-
|
|
7
|
-
export interface State {
|
|
8
|
-
next?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const StateParser = {
|
|
12
|
-
parse: (state: string) => {
|
|
13
|
-
return JSON.parse(decodeURIComponent(atob(state))) as State;
|
|
14
|
-
},
|
|
15
|
-
stringify: (state: State) => {
|
|
16
|
-
return btoa(encodeURIComponent(JSON.stringify(state)));
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Helper function to chunk a value into multiple cookies
|
|
21
|
-
const chunkValue = (value: string): string[] => {
|
|
22
|
-
if (value.length <= MAX_COOKIE_SIZE) {
|
|
23
|
-
return [value];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const chunks: string[] = [];
|
|
27
|
-
for (let i = 0; i < value.length; i += MAX_COOKIE_SIZE) {
|
|
28
|
-
chunks.push(value.slice(i, i + MAX_COOKIE_SIZE));
|
|
29
|
-
}
|
|
30
|
-
return chunks;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Helper function to reassemble chunked cookies
|
|
34
|
-
const reassembleChunkedCookies = (
|
|
35
|
-
cookies: Record<string, string>,
|
|
36
|
-
baseName: string,
|
|
37
|
-
): string | undefined => {
|
|
38
|
-
// First try the base cookie (non-chunked)
|
|
39
|
-
if (cookies[baseName]) {
|
|
40
|
-
return cookies[baseName];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Try to reassemble from chunks
|
|
44
|
-
const chunks: string[] = [];
|
|
45
|
-
let index = 0;
|
|
46
|
-
|
|
47
|
-
while (true) {
|
|
48
|
-
const chunkName = `${baseName}_${index}`;
|
|
49
|
-
if (!cookies[chunkName]) {
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
chunks.push(cookies[chunkName]);
|
|
53
|
-
index++;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return chunks.length > 0 ? chunks.join("") : undefined;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Helper function to parse cookies from request
|
|
60
|
-
const parseCookies = (cookieHeader: string): Record<string, string> => {
|
|
61
|
-
const cookies: Record<string, string> = {};
|
|
62
|
-
if (!cookieHeader) return cookies;
|
|
63
|
-
|
|
64
|
-
cookieHeader.split(";").forEach((cookie) => {
|
|
65
|
-
const [name, ...rest] = cookie.trim().split("=");
|
|
66
|
-
if (name && rest.length > 0) {
|
|
67
|
-
cookies[name] = decodeURIComponent(rest.join("="));
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
return cookies;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const parseJWK = (jwk: string): JWK => JSON.parse(atob(jwk)) as JWK;
|
|
75
|
-
|
|
76
|
-
export const getReqToken = async (req: Request, env: DefaultEnv) => {
|
|
77
|
-
const token = () => {
|
|
78
|
-
// First try to get token from Authorization header
|
|
79
|
-
const authHeader = req.headers.get("Authorization");
|
|
80
|
-
if (authHeader) {
|
|
81
|
-
return authHeader.split(" ")[1];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// If not found, try to get from cookie
|
|
85
|
-
const cookieHeader = req.headers.get("Cookie");
|
|
86
|
-
if (cookieHeader) {
|
|
87
|
-
const cookies = parseCookies(cookieHeader);
|
|
88
|
-
return reassembleChunkedCookies(cookies, DECO_APP_AUTH_COOKIE_NAME);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return undefined;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const authToken = token();
|
|
95
|
-
if (!authToken) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
env.DECO_API_JWT_PUBLIC_KEY &&
|
|
100
|
-
(await jwtVerify(authToken, parseJWK(env.DECO_API_JWT_PUBLIC_KEY), {
|
|
101
|
-
issuer: "https://api.decocms.com",
|
|
102
|
-
algorithms: ["RS256"],
|
|
103
|
-
typ: "JWT",
|
|
104
|
-
}).catch((err) => {
|
|
105
|
-
console.error(
|
|
106
|
-
`[auth-token]: error validating: ${err} ${env.DECO_API_JWT_PUBLIC_KEY}`,
|
|
107
|
-
);
|
|
108
|
-
}));
|
|
109
|
-
|
|
110
|
-
return authToken;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export interface AuthCallbackOptions {
|
|
114
|
-
apiUrl?: string;
|
|
115
|
-
appName: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export const handleAuthCallback = async (
|
|
119
|
-
req: Request,
|
|
120
|
-
options: AuthCallbackOptions,
|
|
121
|
-
): Promise<Response> => {
|
|
122
|
-
const url = new URL(req.url);
|
|
123
|
-
const code = url.searchParams.get("code");
|
|
124
|
-
const state = url.searchParams.get("state");
|
|
125
|
-
|
|
126
|
-
if (!code) {
|
|
127
|
-
return new Response("Missing authorization code", { status: 400 });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Parse state to get the next URL
|
|
131
|
-
let next = "/";
|
|
132
|
-
if (state) {
|
|
133
|
-
try {
|
|
134
|
-
const parsedState = StateParser.parse(state);
|
|
135
|
-
next = parsedState.next || "/";
|
|
136
|
-
} catch {
|
|
137
|
-
// ignore parse errors
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
// Exchange code for token
|
|
143
|
-
const apiUrl = options.apiUrl ?? "https://api.decocms.com";
|
|
144
|
-
const exchangeResponse = await fetch(`${apiUrl}/apps/code-exchange`, {
|
|
145
|
-
method: "POST",
|
|
146
|
-
headers: {
|
|
147
|
-
"Content-Type": "application/json",
|
|
148
|
-
},
|
|
149
|
-
body: JSON.stringify({
|
|
150
|
-
code,
|
|
151
|
-
client_id: options.appName,
|
|
152
|
-
}),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (!exchangeResponse.ok) {
|
|
156
|
-
console.error(
|
|
157
|
-
"authentication failed",
|
|
158
|
-
code,
|
|
159
|
-
options.appName,
|
|
160
|
-
await exchangeResponse.text().catch((_) => ""),
|
|
161
|
-
);
|
|
162
|
-
return new Response("Authentication failed", { status: 401 });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const { access_token } = (await exchangeResponse.json()) as {
|
|
166
|
-
access_token: string;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
if (!access_token) {
|
|
170
|
-
return new Response("No access token received", { status: 401 });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Chunk the token if it's too large
|
|
174
|
-
const chunks = chunkValue(access_token);
|
|
175
|
-
const headers = new Headers();
|
|
176
|
-
headers.set("Location", next);
|
|
177
|
-
|
|
178
|
-
// Set cookies for each chunk
|
|
179
|
-
if (chunks.length === 1) {
|
|
180
|
-
// Single cookie for small tokens
|
|
181
|
-
headers.set(
|
|
182
|
-
"Set-Cookie",
|
|
183
|
-
`${DECO_APP_AUTH_COOKIE_NAME}=${access_token}; HttpOnly; SameSite=None; Secure; Path=/`,
|
|
184
|
-
);
|
|
185
|
-
} else {
|
|
186
|
-
// Multiple cookies for large tokens
|
|
187
|
-
chunks.forEach((chunk, index) => {
|
|
188
|
-
headers.append(
|
|
189
|
-
"Set-Cookie",
|
|
190
|
-
`${DECO_APP_AUTH_COOKIE_NAME}_${index}=${chunk}; HttpOnly; SameSite=None; Secure; Path=/`,
|
|
191
|
-
);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return new Response(null, {
|
|
196
|
-
status: 302,
|
|
197
|
-
headers,
|
|
198
|
-
});
|
|
199
|
-
} catch (err) {
|
|
200
|
-
return new Response(`Authentication failed ${err}`, { status: 500 });
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const removeAuthCookie = (headers: Headers) => {
|
|
205
|
-
// Clear the base cookie
|
|
206
|
-
headers.append(
|
|
207
|
-
"Set-Cookie",
|
|
208
|
-
`${DECO_APP_AUTH_COOKIE_NAME}=; HttpOnly; SameSite=None; Secure; Path=/; Max-Age=0`,
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// Clear all potential chunked cookies
|
|
212
|
-
// We'll try to clear up to 10 chunks (which would support tokens up to 40KB)
|
|
213
|
-
// This is a reasonable upper limit
|
|
214
|
-
for (let i = 0; i < 10; i++) {
|
|
215
|
-
headers.append(
|
|
216
|
-
"Set-Cookie",
|
|
217
|
-
`${DECO_APP_AUTH_COOKIE_NAME}_${i}=; HttpOnly; SameSite=None; Secure; Path=/; Max-Age=0`,
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
export const handleLogout = (req: Request) => {
|
|
223
|
-
const url = new URL(req.url);
|
|
224
|
-
const next = url.searchParams.get("next");
|
|
225
|
-
const redirectTo = new URL("/", url);
|
|
226
|
-
const headers = new Headers();
|
|
227
|
-
removeAuthCookie(headers);
|
|
228
|
-
headers.set("Location", next ?? redirectTo.href);
|
|
229
|
-
return new Response(null, {
|
|
230
|
-
status: 302,
|
|
231
|
-
headers,
|
|
232
|
-
});
|
|
233
|
-
};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// Helper functions for DeconfigResource
|
|
2
|
-
|
|
3
|
-
export const normalizeDirectory = (dir: string) => {
|
|
4
|
-
// Ensure directory starts with / and doesn't end with /
|
|
5
|
-
const normalized = dir.startsWith("/") ? dir : `/${dir}`;
|
|
6
|
-
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export const ResourcePath = {
|
|
10
|
-
build: (directory: string, resourceId: string) => {
|
|
11
|
-
const normalizedDir = normalizeDirectory(directory);
|
|
12
|
-
return `${normalizedDir}/${resourceId}.json`;
|
|
13
|
-
},
|
|
14
|
-
extract: (path: string) => {
|
|
15
|
-
const match = path.match(/^(.+)\/(.+)\.json$/);
|
|
16
|
-
if (!match) {
|
|
17
|
-
throw new Error("Invalid resource path");
|
|
18
|
-
}
|
|
19
|
-
return { directory: match[1], resourceId: match[2] };
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const ResourceUri = {
|
|
24
|
-
build: (integrationId: string, resourceName: string, resourceId: string) => {
|
|
25
|
-
return `rsc://${integrationId}/${resourceName}/${resourceId}`;
|
|
26
|
-
},
|
|
27
|
-
unwind: (uri: string) => {
|
|
28
|
-
const match = uri.match(/^rsc:\/\/[^/]+\/([^/]+)\/(.+)$/);
|
|
29
|
-
if (!match) {
|
|
30
|
-
throw new Error("Invalid Resources 2.0 URI format");
|
|
31
|
-
}
|
|
32
|
-
return { resourceName: match[1], resourceId: match[2] };
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export function getMetadataValue(metadata: unknown, key: string): unknown {
|
|
37
|
-
if (!metadata || typeof metadata !== "object") return undefined;
|
|
38
|
-
const metaObj = metadata as Record<string, unknown>;
|
|
39
|
-
if (key in metaObj) return metaObj[key];
|
|
40
|
-
const nested = metaObj.metadata;
|
|
41
|
-
if (nested && typeof nested === "object" && key in nested) {
|
|
42
|
-
return (nested as Record<string, unknown>)[key];
|
|
43
|
-
}
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function getMetadataString(
|
|
48
|
-
metadata: unknown,
|
|
49
|
-
key: string,
|
|
50
|
-
): string | undefined {
|
|
51
|
-
const value = getMetadataValue(metadata, key);
|
|
52
|
-
return typeof value === "string" ? value : undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const toAsyncIterator = <T>(
|
|
56
|
-
emitter: EventSource,
|
|
57
|
-
eventType: string = "message",
|
|
58
|
-
): AsyncIterable<T> => {
|
|
59
|
-
const queue: T[] = [];
|
|
60
|
-
let done = false;
|
|
61
|
-
let waitPromise: ((data?: T) => void) | null = null;
|
|
62
|
-
|
|
63
|
-
const triggerLoop = () => {
|
|
64
|
-
if (waitPromise) {
|
|
65
|
-
waitPromise();
|
|
66
|
-
waitPromise = null;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const messageHandler = (data: MessageEvent) => {
|
|
71
|
-
try {
|
|
72
|
-
queue.push(JSON.parse(data.data));
|
|
73
|
-
} catch {
|
|
74
|
-
// Silently ignore malformed data or optionally log error
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
triggerLoop();
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const errorHandler = () => {
|
|
81
|
-
done = true;
|
|
82
|
-
triggerLoop();
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
emitter.addEventListener(eventType, messageHandler);
|
|
86
|
-
emitter.addEventListener("error", errorHandler);
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
async *[Symbol.asyncIterator]() {
|
|
90
|
-
try {
|
|
91
|
-
while (true) {
|
|
92
|
-
const value = queue.shift();
|
|
93
|
-
if (value) {
|
|
94
|
-
yield value;
|
|
95
|
-
} else {
|
|
96
|
-
if (done) return;
|
|
97
|
-
await new Promise((resolve) => (waitPromise = resolve));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} finally {
|
|
101
|
-
emitter.removeEventListener(eventType, messageHandler);
|
|
102
|
-
emitter.removeEventListener("error", errorHandler);
|
|
103
|
-
emitter.close();
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./resources.ts";
|