@decocms/runtime 1.2.6 → 1.2.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
@@ -31,6 +31,11 @@
31
31
  "engines": {
32
32
  "node": ">=24.0.0"
33
33
  },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/decocms/mesh.git",
37
+ "directory": "packages/runtime"
38
+ },
34
39
  "publishConfig": {
35
40
  "access": "public"
36
41
  }
@@ -1,11 +1,16 @@
1
1
  // heavily inspired by https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/scripts/generate-json-schema.ts
2
2
  import { writeFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { createGenerator } from "ts-json-schema-generator";
5
6
  import type { Config, Schema } from "ts-json-schema-generator";
6
7
 
8
+ // Use standard ESM __dirname pattern for cross-runtime compatibility
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+
7
11
  const config: Config = {
8
- path: join(import.meta.dirname!, "../src/wrangler.ts"),
12
+ path: join(__dirname, "../src/wrangler.ts"),
13
+ tsconfig: join(__dirname, "../tsconfig.json"),
9
14
  type: "WranglerConfig",
10
15
  skipTypeCheck: true,
11
16
  };
@@ -19,6 +24,6 @@ const schema = applyFormattingRules(
19
24
  );
20
25
 
21
26
  writeFileSync(
22
- join(import.meta.dirname!, "../config-schema.json"),
27
+ join(__dirname, "../config-schema.json"),
23
28
  JSON.stringify(schema, null, 2),
24
29
  );
package/src/bindings.ts CHANGED
@@ -108,6 +108,12 @@ export const proxyConnectionForId = (
108
108
  headers ??= {};
109
109
  headers.cookie = ctx.cookie;
110
110
  }
111
+
112
+ if (ctx.token) {
113
+ headers ??= {};
114
+ headers["x-mesh-token"] = ctx.token;
115
+ }
116
+
111
117
  return {
112
118
  type: "HTTP",
113
119
  url: new URL(`/mcp/${connectionId}`, ctx.meshUrl).href,
package/src/oauth.ts CHANGED
@@ -285,24 +285,41 @@ export function createOAuthHandlers(oauth: OAuthConfig) {
285
285
  const handleToken = async (req: Request): Promise<Response> => {
286
286
  try {
287
287
  const contentType = req.headers.get("content-type") ?? "";
288
- let body: Record<string, string>;
288
+ let body: Record<string, unknown>;
289
289
 
290
290
  if (contentType.includes("application/x-www-form-urlencoded")) {
291
291
  const formData = await req.formData();
292
- body = Object.fromEntries(formData.entries()) as Record<string, string>;
292
+ body = Object.fromEntries(formData.entries());
293
293
  } else {
294
- body = await req.json();
294
+ const jsonBody = await req.json();
295
+ if (
296
+ typeof jsonBody !== "object" ||
297
+ jsonBody === null ||
298
+ Array.isArray(jsonBody)
299
+ ) {
300
+ return Response.json(
301
+ {
302
+ error: "invalid_request",
303
+ error_description: "Request body must be a JSON object",
304
+ },
305
+ { status: 400 },
306
+ );
307
+ }
308
+ body = jsonBody as Record<string, unknown>;
295
309
  }
296
310
 
311
+ // Extract and validate OAuth parameters
312
+ // Per RFC 6749, all parameters should be strings, but we validate at runtime
297
313
  const { code, code_verifier, grant_type, refresh_token } = body;
298
314
 
299
315
  // Handle refresh_token grant type
300
316
  if (grant_type === "refresh_token") {
301
- if (!refresh_token) {
317
+ if (typeof refresh_token !== "string" || !refresh_token) {
302
318
  return Response.json(
303
319
  {
304
320
  error: "invalid_request",
305
- error_description: "refresh_token is required",
321
+ error_description:
322
+ "refresh_token is required and must be a string",
306
323
  },
307
324
  { status: 400 },
308
325
  );
@@ -356,9 +373,12 @@ export function createOAuthHandlers(oauth: OAuthConfig) {
356
373
  );
357
374
  }
358
375
 
359
- if (!code) {
376
+ if (typeof code !== "string" || !code) {
360
377
  return Response.json(
361
- { error: "invalid_request", error_description: "code is required" },
378
+ {
379
+ error: "invalid_request",
380
+ error_description: "code is required and must be a string",
381
+ },
362
382
  { status: 400 },
363
383
  );
364
384
  }
@@ -377,11 +397,11 @@ export function createOAuthHandlers(oauth: OAuthConfig) {
377
397
 
378
398
  // Verify PKCE if code challenge was provided
379
399
  if (payload.codeChallenge) {
380
- if (!code_verifier) {
400
+ if (typeof code_verifier !== "string" || !code_verifier) {
381
401
  return Response.json(
382
402
  {
383
403
  error: "invalid_grant",
384
- error_description: "code_verifier required",
404
+ error_description: "code_verifier required and must be a string",
385
405
  },
386
406
  { status: 400 },
387
407
  );
package/src/tools.ts CHANGED
@@ -345,39 +345,70 @@ export interface OnChangeCallback<TState> {
345
345
  scopes: string[];
346
346
  }
347
347
 
348
+ /**
349
+ * OAuth 2.0 Token Exchange Parameters
350
+ * Parameters passed to exchangeCode() for token retrieval
351
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
352
+ */
348
353
  export interface OAuthParams {
354
+ /** REQUIRED - The authorization code received from the authorization server */
349
355
  code: string;
356
+ /** OPTIONAL - PKCE code verifier (RFC 7636) */
350
357
  code_verifier?: string;
358
+ /** OPTIONAL - Code challenge method: S256 (SHA-256) or plain */
351
359
  code_challenge_method?: "S256" | "plain";
352
360
  /**
353
- * The redirect_uri used in the authorization request (without state/extra params).
354
- * This is the clean callback URL that should be used for token exchange.
361
+ * OPTIONAL - The redirect_uri used in the authorization request
362
+ * MUST be identical if included in the authorization request
355
363
  */
356
364
  redirect_uri?: string;
357
365
  }
358
366
 
367
+ /**
368
+ * OAuth 2.0 Token Response
369
+ * Response from the authorization server's token endpoint
370
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
371
+ */
359
372
  export interface OAuthTokenResponse {
373
+ /** REQUIRED - The access token issued by the authorization server */
360
374
  access_token: string;
375
+ /** REQUIRED - Type of token (usually "Bearer" per RFC 6750) */
361
376
  token_type: string;
377
+ /** RECOMMENDED - Lifetime in seconds of the access token */
362
378
  expires_in?: number;
379
+ /** OPTIONAL - Used to obtain new access tokens (if applicable) */
363
380
  refresh_token?: string;
381
+ /** OPTIONAL - Scope of the access token (if different from requested) */
364
382
  scope?: string;
383
+ /** Additional provider-specific fields */
365
384
  [key: string]: unknown;
366
385
  }
367
386
 
368
387
  /**
369
- * OAuth client for dynamic client registration (RFC7591)
388
+ * OAuth 2.0 Client Metadata (Dynamic Client Registration)
389
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
390
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1
370
391
  */
371
392
  export interface OAuthClient {
393
+ /** REQUIRED - OAuth 2.0 client identifier string */
372
394
  client_id: string;
395
+ /** OPTIONAL - OAuth 2.0 client secret string (confidential clients) */
373
396
  client_secret?: string;
397
+ /** OPTIONAL - Human-readable name of the client */
374
398
  client_name?: string;
399
+ /** REQUIRED - Array of redirect URIs for use in redirect-based flows */
375
400
  redirect_uris: string[];
401
+ /** OPTIONAL - Array of OAuth 2.0 grant types (e.g., "authorization_code", "refresh_token") */
376
402
  grant_types?: string[];
403
+ /** OPTIONAL - Array of response types (e.g., "code", "token") */
377
404
  response_types?: string[];
405
+ /** OPTIONAL - Authentication method for the token endpoint (e.g., "client_secret_basic", "none") */
378
406
  token_endpoint_auth_method?: string;
407
+ /** OPTIONAL - Space-separated list of scope values */
379
408
  scope?: string;
409
+ /** OPTIONAL - Time at which the client identifier was issued (Unix timestamp) */
380
410
  client_id_issued_at?: number;
411
+ /** OPTIONAL - Time at which the client secret expires (Unix timestamp, 0 = never) */
381
412
  client_secret_expires_at?: number;
382
413
  }
383
414