@rstreamlabs/tunnels 1.0.8 → 1.0.10

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/dist/index.js CHANGED
@@ -162,8 +162,7 @@ var tunnelSchema = z2.object({
162
162
  tls_alpns: z2.array(z2.string()).optional(),
163
163
  tls_min_version: z2.string().optional(),
164
164
  tls_ciphers: z2.array(z2.string()).optional(),
165
- mtls: z2.boolean().optional(),
166
- mtls_ca_cert_pem: z2.string().optional(),
165
+ mtls_auth: z2.boolean().optional(),
167
166
  http_version: z2.enum(["http/1.1", "h2", "h2c", "h3"]).optional(),
168
167
  http_use_tls: z2.boolean().optional(),
169
168
  upstream_tls: z2.boolean().optional(),
@@ -263,10 +262,10 @@ var authTokenScopesSchema = z3.object({
263
262
  });
264
263
  }
265
264
  });
266
- var authTokenTunnelGrantSchema = z3.object({
265
+ var authTokenTunnelGrantLeafSchema = z3.object({
267
266
  workspaces: z3.array(nonEmptyStringSchema).min(1).optional(),
268
267
  projects: z3.array(nonEmptyStringSchema).min(1).optional(),
269
- scopes: authTokenScopesSchema
268
+ scopes: authTokenScopesSchema.optional()
270
269
  }).strict().superRefine((grant, ctx) => {
271
270
  if (grant.workspaces !== void 0 && grant.projects !== void 0) {
272
271
  ctx.addIssue({
@@ -275,18 +274,25 @@ var authTokenTunnelGrantSchema = z3.object({
275
274
  path: ["projects"]
276
275
  });
277
276
  }
278
- if (grant.workspaces === void 0 && grant.projects === void 0) {
277
+ if (grant.workspaces === void 0 && grant.projects === void 0 && grant.scopes === void 0) {
279
278
  ctx.addIssue({
280
279
  code: z3.ZodIssueCode.custom,
281
- message: "A grant must target workspaces or projects.",
280
+ message: "A grant must target workspaces, projects, or scopes.",
282
281
  path: ["workspaces"]
283
282
  });
284
283
  }
285
284
  });
285
+ var authTokenTunnelGrantSchema = z3.lazy(
286
+ () => z3.union([
287
+ authTokenTunnelGrantLeafSchema,
288
+ z3.object({ AND: z3.array(authTokenTunnelGrantSchema).min(1) }).strict(),
289
+ z3.object({ OR: z3.array(authTokenTunnelGrantSchema).min(1) }).strict()
290
+ ])
291
+ );
286
292
  var authTokenCommonSchema = {
287
293
  aud: z3.union([z3.string(), z3.array(z3.string())]).optional(),
288
294
  exp: z3.number().optional(),
289
- tunnelsGrants: z3.array(authTokenTunnelGrantSchema).optional(),
295
+ tunnelsGrants: authTokenTunnelGrantSchema.optional(),
290
296
  iat: z3.number().optional(),
291
297
  iss: z3.string().optional(),
292
298
  jti: z3.string().optional(),
@@ -305,6 +311,7 @@ var authTokenSchema = z3.union([
305
311
  }).strict(),
306
312
  z3.object({
307
313
  type: z3.literal("pat"),
314
+ tokendpoint: z3.string().optional(),
308
315
  token_endpoint: z3.string().optional(),
309
316
  ...authTokenCommonSchema
310
317
  }).strict(),
@@ -319,7 +326,7 @@ var tokenSchema = authTokenSchema;
319
326
  var createAuthTokenParamsSchema = z3.object({
320
327
  expires_in: z3.number().int().min(1).max(3600).default(60),
321
328
  scopes: authTokenScopesSchema.optional(),
322
- tunnelsGrants: z3.array(authTokenTunnelGrantSchema).min(1).optional(),
329
+ tunnelsGrants: authTokenTunnelGrantSchema.optional(),
323
330
  metadata: z3.unknown().optional()
324
331
  // Additional metadata
325
332
  }).strict().superRefine((params, ctx) => {
@@ -357,10 +364,30 @@ var RstreamAuthResource = class {
357
364
  );
358
365
  }
359
366
  const engine = options?.engine ?? await this.client.getEngine().catch(() => void 0);
367
+ const scopedOptions = await this.resolveScopeOptions(params, options);
360
368
  return createAuthTokenFromClientCredentials(credentials, params, {
361
- engine
369
+ engine,
370
+ ...scopedOptions
362
371
  });
363
372
  }
373
+ async resolveScopeOptions(params, options) {
374
+ const tokenParams = createAuthTokenParamsSchema.parse(params);
375
+ if (options?.projectScoped === false) {
376
+ return options ?? {};
377
+ }
378
+ if (options?.projectId !== void 0 || options?.workspaceId !== void 0) {
379
+ return options;
380
+ }
381
+ if (tokenParams.scopes === void 0 && !tunnelGrantsNeedDefaultTarget(tokenParams.tunnelsGrants)) {
382
+ return options ?? {};
383
+ }
384
+ const target = await this.client.getEngineAuthTokenTarget();
385
+ return {
386
+ ...options,
387
+ projectId: target.projects?.[0],
388
+ workspaceId: target.workspaces?.[0]
389
+ };
390
+ }
364
391
  };
365
392
  function createAuthTokenFromClientCredentials(credentials, params, options) {
366
393
  const tokenParams = createAuthTokenParamsSchema.parse(params);
@@ -380,13 +407,66 @@ function createAuthTokenFromClientCredentials(credentials, params, options) {
380
407
  function normalizeAuthTokenTunnelGrants(params, options) {
381
408
  const tunnelsGrants = params.tunnelsGrants;
382
409
  if (tunnelsGrants !== void 0) {
383
- return [...tunnelsGrants];
410
+ return applyDefaultTunnelGrantTarget(tunnelsGrants, options);
384
411
  }
385
412
  if (params.scopes) {
386
- return [{ ...normalizeTunnelGrantTarget(options), scopes: params.scopes }];
413
+ return { ...normalizeTunnelGrantTarget(options), scopes: params.scopes };
387
414
  }
388
415
  return void 0;
389
416
  }
417
+ function applyDefaultTunnelGrantTarget(grants, options, inheritedTarget = false) {
418
+ if (options?.projectScoped === false) {
419
+ return grants;
420
+ }
421
+ if ("AND" in grants) {
422
+ const branchHasTarget = inheritedTarget || grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
423
+ return {
424
+ AND: grants.AND.map(
425
+ (grant) => applyDefaultTunnelGrantTarget(grant, options, branchHasTarget)
426
+ )
427
+ };
428
+ }
429
+ if ("OR" in grants) {
430
+ return {
431
+ OR: grants.OR.map(
432
+ (grant) => applyDefaultTunnelGrantTarget(grant, options, inheritedTarget)
433
+ )
434
+ };
435
+ }
436
+ if (grants.scopes === void 0 || tunnelGrantHasTarget(grants) || inheritedTarget) {
437
+ return grants;
438
+ }
439
+ return { ...normalizeTunnelGrantTarget(options), scopes: grants.scopes };
440
+ }
441
+ function tunnelGrantsNeedDefaultTarget(grants, inheritedTarget = false) {
442
+ if (grants === void 0) {
443
+ return false;
444
+ }
445
+ if ("AND" in grants) {
446
+ const branchHasTarget = inheritedTarget || grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
447
+ return grants.AND.some(
448
+ (grant) => tunnelGrantsNeedDefaultTarget(grant, branchHasTarget)
449
+ );
450
+ }
451
+ if ("OR" in grants) {
452
+ return grants.OR.some(
453
+ (grant) => tunnelGrantsNeedDefaultTarget(grant, inheritedTarget)
454
+ );
455
+ }
456
+ return grants.scopes !== void 0 && !tunnelGrantHasTarget(grants) && !inheritedTarget;
457
+ }
458
+ function tunnelGrantHasTarget(grant) {
459
+ return grant.projects !== void 0 || grant.workspaces !== void 0;
460
+ }
461
+ function tunnelGrantTreeHasTarget(grants) {
462
+ if ("AND" in grants) {
463
+ return grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
464
+ }
465
+ if ("OR" in grants) {
466
+ return grants.OR.some((grant) => tunnelGrantTreeHasTarget(grant));
467
+ }
468
+ return tunnelGrantHasTarget(grants);
469
+ }
390
470
  function normalizeTunnelGrantTarget(options) {
391
471
  const projectId = normalizeOptionalString(options?.projectId);
392
472
  const workspaceId = normalizeOptionalString(options?.workspaceId);
@@ -399,6 +479,9 @@ function normalizeTunnelGrantTarget(options) {
399
479
  if (workspaceId !== void 0) {
400
480
  return { workspaces: [workspaceId] };
401
481
  }
482
+ if (options?.projectScoped === false) {
483
+ return {};
484
+ }
402
485
  throw new Error(
403
486
  "Project ID or workspace ID is required when using scope-only tunnel grants."
404
487
  );
@@ -620,7 +703,7 @@ var routedTunnelSchema = z5.object({
620
703
  httpUseTls: z5.boolean().optional(),
621
704
  upstreamTls: z5.boolean().optional(),
622
705
  tokenAuth: z5.boolean().optional(),
623
- mtls: z5.boolean().optional()
706
+ mtls_auth: z5.boolean().optional()
624
707
  });
625
708
  var routingSchema = z5.discriminatedUnion("decision", [
626
709
  z5.object({ decision: z5.literal("routed"), tunnel: routedTunnelSchema }),
@@ -862,7 +945,7 @@ var engineOpenApiDocument = {
862
945
  info: {
863
946
  title: "rstream Engine API",
864
947
  version: "1.0.0",
865
- description: "Operational data-plane API exposed by rstream-engine. This surface is scoped to the authenticated engine or project endpoint and covers live clients, live tunnels, and state-change streams."
948
+ description: "Operational Engine API exposed by rstream engine. This surface is scoped to the authenticated engine or project endpoint and covers live clients, live tunnels, and state-change streams."
866
949
  },
867
950
  servers: [
868
951
  {
@@ -1179,7 +1262,7 @@ async function resolveTunnelsEngine(config) {
1179
1262
  );
1180
1263
  }
1181
1264
  throw new Error(
1182
- "Engine URL is not defined. Provide an engine, use RSTREAM_ENGINE, or configure a managed project endpoint with control-plane access."
1265
+ "Engine URL is not defined. Provide an engine, use RSTREAM_ENGINE, or configure a managed project endpoint with Control plane API access."
1183
1266
  );
1184
1267
  }
1185
1268
  async function resolveManagedTunnelsProject(config) {
@@ -1255,6 +1338,7 @@ var turnTokenClaimsSchema = z8.object({
1255
1338
  exp: z8.number().int().optional(),
1256
1339
  iat: z8.number().int().optional(),
1257
1340
  token_endpoint: z8.string().regex(/^[0-9a-f]{8}$/i).optional(),
1341
+ tokendpoint: z8.string().regex(/^[0-9a-f]{8}$/i).optional(),
1258
1342
  type: z8.enum(["app", "auth", "pat"])
1259
1343
  });
1260
1344
  var turnCredentialModeSchema = z8.enum(["api", "app", "pat"]);
@@ -1431,7 +1515,8 @@ function createPATTURNCredentials(options) {
1431
1515
  if (!tokenEndpoint) {
1432
1516
  throw new Error("Token endpoint is required for TURN PAT mode.");
1433
1517
  }
1434
- if (claims.token_endpoint !== void 0 && claims.token_endpoint !== tokenEndpoint) {
1518
+ const claimsTokenEndpoint = claims.token_endpoint ?? claims.tokendpoint;
1519
+ if (claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== tokenEndpoint) {
1435
1520
  throw new Error("Token endpoint does not match the PAT token.");
1436
1521
  }
1437
1522
  const now = normalizeNow(options.now);
@@ -1760,12 +1845,10 @@ var RstreamTunnelsClient = class {
1760
1845
  async getEngineAuthTokenParams() {
1761
1846
  const target = await this.getEngineAuthTokenTarget();
1762
1847
  return {
1763
- tunnelsGrants: [
1764
- {
1765
- ...target,
1766
- scopes: engineAuthTokenScopes
1767
- }
1768
- ]
1848
+ tunnelsGrants: {
1849
+ ...target,
1850
+ scopes: engineAuthTokenScopes
1851
+ }
1769
1852
  };
1770
1853
  }
1771
1854
  async getEngineAuthTokenTarget() {
@@ -1883,16 +1966,44 @@ function hasWatchOnlyTunnelGrants(token) {
1883
1966
  if (!hasWatchOnlyPermissions(token.permissions)) {
1884
1967
  return false;
1885
1968
  }
1886
- if (!Array.isArray(token.tunnelsGrants) || token.tunnelsGrants.length === 0) {
1969
+ if (token.tunnelsGrants === void 0) {
1887
1970
  return false;
1888
1971
  }
1889
- return token.tunnelsGrants.every((grant) => {
1890
- const tunnels = grant.scopes?.tunnels;
1891
- if (tunnels === void 0) {
1892
- return false;
1972
+ return hasWatchOnlyTunnelGrant(token.tunnelsGrants);
1973
+ }
1974
+ function hasWatchOnlyTunnelGrant(grant) {
1975
+ return watchGrantBranches(grant).every(
1976
+ (branch) => branch.hasList && !branch.hasMutationOrConnect
1977
+ );
1978
+ }
1979
+ function watchGrantBranches(grant) {
1980
+ if ("AND" in grant) {
1981
+ return grant.AND.reduce(
1982
+ (branches, child) => combineWatchGrantBranches(branches, child),
1983
+ [{ hasList: false, hasMutationOrConnect: false }]
1984
+ );
1985
+ }
1986
+ if ("OR" in grant) {
1987
+ return grant.OR.flatMap(watchGrantBranches);
1988
+ }
1989
+ const tunnels = grant.scopes?.tunnels;
1990
+ if (tunnels === void 0) {
1991
+ return [{ hasList: false, hasMutationOrConnect: false }];
1992
+ }
1993
+ return [
1994
+ {
1995
+ hasList: tunnels.list !== void 0,
1996
+ hasMutationOrConnect: tunnels.create !== void 0 || tunnels.connect !== void 0
1893
1997
  }
1894
- return tunnels.list !== void 0 && tunnels.create === void 0 && tunnels.connect === void 0;
1895
- });
1998
+ ];
1999
+ }
2000
+ function combineWatchGrantBranches(branches, child) {
2001
+ return branches.flatMap(
2002
+ (branch) => watchGrantBranches(child).map((childBranch) => ({
2003
+ hasList: branch.hasList || childBranch.hasList,
2004
+ hasMutationOrConnect: branch.hasMutationOrConnect || childBranch.hasMutationOrConnect
2005
+ }))
2006
+ );
1896
2007
  }
1897
2008
  function hasWatchOnlyPermissions(permissions) {
1898
2009
  if (!Array.isArray(permissions)) {
package/dist/index.mjs CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  tunnelSchema,
17
17
  webhookEventsSchema,
18
18
  wsEventsSchema
19
- } from "./chunk-7RZ743EF.mjs";
19
+ } from "./chunk-NHUJ3FYM.mjs";
20
20
 
21
21
  // src/auth-resource.ts
22
22
  import { createClientCredentialsToken } from "@rstreamlabs/rstream";
@@ -130,10 +130,10 @@ var authTokenScopesSchema = z2.object({
130
130
  });
131
131
  }
132
132
  });
133
- var authTokenTunnelGrantSchema = z2.object({
133
+ var authTokenTunnelGrantLeafSchema = z2.object({
134
134
  workspaces: z2.array(nonEmptyStringSchema).min(1).optional(),
135
135
  projects: z2.array(nonEmptyStringSchema).min(1).optional(),
136
- scopes: authTokenScopesSchema
136
+ scopes: authTokenScopesSchema.optional()
137
137
  }).strict().superRefine((grant, ctx) => {
138
138
  if (grant.workspaces !== void 0 && grant.projects !== void 0) {
139
139
  ctx.addIssue({
@@ -142,18 +142,25 @@ var authTokenTunnelGrantSchema = z2.object({
142
142
  path: ["projects"]
143
143
  });
144
144
  }
145
- if (grant.workspaces === void 0 && grant.projects === void 0) {
145
+ if (grant.workspaces === void 0 && grant.projects === void 0 && grant.scopes === void 0) {
146
146
  ctx.addIssue({
147
147
  code: z2.ZodIssueCode.custom,
148
- message: "A grant must target workspaces or projects.",
148
+ message: "A grant must target workspaces, projects, or scopes.",
149
149
  path: ["workspaces"]
150
150
  });
151
151
  }
152
152
  });
153
+ var authTokenTunnelGrantSchema = z2.lazy(
154
+ () => z2.union([
155
+ authTokenTunnelGrantLeafSchema,
156
+ z2.object({ AND: z2.array(authTokenTunnelGrantSchema).min(1) }).strict(),
157
+ z2.object({ OR: z2.array(authTokenTunnelGrantSchema).min(1) }).strict()
158
+ ])
159
+ );
153
160
  var authTokenCommonSchema = {
154
161
  aud: z2.union([z2.string(), z2.array(z2.string())]).optional(),
155
162
  exp: z2.number().optional(),
156
- tunnelsGrants: z2.array(authTokenTunnelGrantSchema).optional(),
163
+ tunnelsGrants: authTokenTunnelGrantSchema.optional(),
157
164
  iat: z2.number().optional(),
158
165
  iss: z2.string().optional(),
159
166
  jti: z2.string().optional(),
@@ -172,6 +179,7 @@ var authTokenSchema = z2.union([
172
179
  }).strict(),
173
180
  z2.object({
174
181
  type: z2.literal("pat"),
182
+ tokendpoint: z2.string().optional(),
175
183
  token_endpoint: z2.string().optional(),
176
184
  ...authTokenCommonSchema
177
185
  }).strict(),
@@ -186,7 +194,7 @@ var tokenSchema = authTokenSchema;
186
194
  var createAuthTokenParamsSchema = z2.object({
187
195
  expires_in: z2.number().int().min(1).max(3600).default(60),
188
196
  scopes: authTokenScopesSchema.optional(),
189
- tunnelsGrants: z2.array(authTokenTunnelGrantSchema).min(1).optional(),
197
+ tunnelsGrants: authTokenTunnelGrantSchema.optional(),
190
198
  metadata: z2.unknown().optional()
191
199
  // Additional metadata
192
200
  }).strict().superRefine((params, ctx) => {
@@ -224,10 +232,30 @@ var RstreamAuthResource = class {
224
232
  );
225
233
  }
226
234
  const engine = options?.engine ?? await this.client.getEngine().catch(() => void 0);
235
+ const scopedOptions = await this.resolveScopeOptions(params, options);
227
236
  return createAuthTokenFromClientCredentials(credentials, params, {
228
- engine
237
+ engine,
238
+ ...scopedOptions
229
239
  });
230
240
  }
241
+ async resolveScopeOptions(params, options) {
242
+ const tokenParams = createAuthTokenParamsSchema.parse(params);
243
+ if (options?.projectScoped === false) {
244
+ return options ?? {};
245
+ }
246
+ if (options?.projectId !== void 0 || options?.workspaceId !== void 0) {
247
+ return options;
248
+ }
249
+ if (tokenParams.scopes === void 0 && !tunnelGrantsNeedDefaultTarget(tokenParams.tunnelsGrants)) {
250
+ return options ?? {};
251
+ }
252
+ const target = await this.client.getEngineAuthTokenTarget();
253
+ return {
254
+ ...options,
255
+ projectId: target.projects?.[0],
256
+ workspaceId: target.workspaces?.[0]
257
+ };
258
+ }
231
259
  };
232
260
  function createAuthTokenFromClientCredentials(credentials, params, options) {
233
261
  const tokenParams = createAuthTokenParamsSchema.parse(params);
@@ -247,13 +275,66 @@ function createAuthTokenFromClientCredentials(credentials, params, options) {
247
275
  function normalizeAuthTokenTunnelGrants(params, options) {
248
276
  const tunnelsGrants = params.tunnelsGrants;
249
277
  if (tunnelsGrants !== void 0) {
250
- return [...tunnelsGrants];
278
+ return applyDefaultTunnelGrantTarget(tunnelsGrants, options);
251
279
  }
252
280
  if (params.scopes) {
253
- return [{ ...normalizeTunnelGrantTarget(options), scopes: params.scopes }];
281
+ return { ...normalizeTunnelGrantTarget(options), scopes: params.scopes };
254
282
  }
255
283
  return void 0;
256
284
  }
285
+ function applyDefaultTunnelGrantTarget(grants, options, inheritedTarget = false) {
286
+ if (options?.projectScoped === false) {
287
+ return grants;
288
+ }
289
+ if ("AND" in grants) {
290
+ const branchHasTarget = inheritedTarget || grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
291
+ return {
292
+ AND: grants.AND.map(
293
+ (grant) => applyDefaultTunnelGrantTarget(grant, options, branchHasTarget)
294
+ )
295
+ };
296
+ }
297
+ if ("OR" in grants) {
298
+ return {
299
+ OR: grants.OR.map(
300
+ (grant) => applyDefaultTunnelGrantTarget(grant, options, inheritedTarget)
301
+ )
302
+ };
303
+ }
304
+ if (grants.scopes === void 0 || tunnelGrantHasTarget(grants) || inheritedTarget) {
305
+ return grants;
306
+ }
307
+ return { ...normalizeTunnelGrantTarget(options), scopes: grants.scopes };
308
+ }
309
+ function tunnelGrantsNeedDefaultTarget(grants, inheritedTarget = false) {
310
+ if (grants === void 0) {
311
+ return false;
312
+ }
313
+ if ("AND" in grants) {
314
+ const branchHasTarget = inheritedTarget || grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
315
+ return grants.AND.some(
316
+ (grant) => tunnelGrantsNeedDefaultTarget(grant, branchHasTarget)
317
+ );
318
+ }
319
+ if ("OR" in grants) {
320
+ return grants.OR.some(
321
+ (grant) => tunnelGrantsNeedDefaultTarget(grant, inheritedTarget)
322
+ );
323
+ }
324
+ return grants.scopes !== void 0 && !tunnelGrantHasTarget(grants) && !inheritedTarget;
325
+ }
326
+ function tunnelGrantHasTarget(grant) {
327
+ return grant.projects !== void 0 || grant.workspaces !== void 0;
328
+ }
329
+ function tunnelGrantTreeHasTarget(grants) {
330
+ if ("AND" in grants) {
331
+ return grants.AND.some((grant) => tunnelGrantTreeHasTarget(grant));
332
+ }
333
+ if ("OR" in grants) {
334
+ return grants.OR.some((grant) => tunnelGrantTreeHasTarget(grant));
335
+ }
336
+ return tunnelGrantHasTarget(grants);
337
+ }
257
338
  function normalizeTunnelGrantTarget(options) {
258
339
  const projectId = normalizeOptionalString(options?.projectId);
259
340
  const workspaceId = normalizeOptionalString(options?.workspaceId);
@@ -266,6 +347,9 @@ function normalizeTunnelGrantTarget(options) {
266
347
  if (workspaceId !== void 0) {
267
348
  return { workspaces: [workspaceId] };
268
349
  }
350
+ if (options?.projectScoped === false) {
351
+ return {};
352
+ }
269
353
  throw new Error(
270
354
  "Project ID or workspace ID is required when using scope-only tunnel grants."
271
355
  );
@@ -398,7 +482,7 @@ async function resolveTunnelsEngine(config) {
398
482
  );
399
483
  }
400
484
  throw new Error(
401
- "Engine URL is not defined. Provide an engine, use RSTREAM_ENGINE, or configure a managed project endpoint with control-plane access."
485
+ "Engine URL is not defined. Provide an engine, use RSTREAM_ENGINE, or configure a managed project endpoint with Control plane API access."
402
486
  );
403
487
  }
404
488
  async function resolveManagedTunnelsProject(config) {
@@ -474,6 +558,7 @@ var turnTokenClaimsSchema = z3.object({
474
558
  exp: z3.number().int().optional(),
475
559
  iat: z3.number().int().optional(),
476
560
  token_endpoint: z3.string().regex(/^[0-9a-f]{8}$/i).optional(),
561
+ tokendpoint: z3.string().regex(/^[0-9a-f]{8}$/i).optional(),
477
562
  type: z3.enum(["app", "auth", "pat"])
478
563
  });
479
564
  var turnCredentialModeSchema = z3.enum(["api", "app", "pat"]);
@@ -650,7 +735,8 @@ function createPATTURNCredentials(options) {
650
735
  if (!tokenEndpoint) {
651
736
  throw new Error("Token endpoint is required for TURN PAT mode.");
652
737
  }
653
- if (claims.token_endpoint !== void 0 && claims.token_endpoint !== tokenEndpoint) {
738
+ const claimsTokenEndpoint = claims.token_endpoint ?? claims.tokendpoint;
739
+ if (claimsTokenEndpoint !== void 0 && claimsTokenEndpoint !== tokenEndpoint) {
654
740
  throw new Error("Token endpoint does not match the PAT token.");
655
741
  }
656
742
  const now = normalizeNow(options.now);
@@ -979,12 +1065,10 @@ var RstreamTunnelsClient = class {
979
1065
  async getEngineAuthTokenParams() {
980
1066
  const target = await this.getEngineAuthTokenTarget();
981
1067
  return {
982
- tunnelsGrants: [
983
- {
984
- ...target,
985
- scopes: engineAuthTokenScopes
986
- }
987
- ]
1068
+ tunnelsGrants: {
1069
+ ...target,
1070
+ scopes: engineAuthTokenScopes
1071
+ }
988
1072
  };
989
1073
  }
990
1074
  async getEngineAuthTokenTarget() {
@@ -1102,16 +1186,44 @@ function hasWatchOnlyTunnelGrants(token) {
1102
1186
  if (!hasWatchOnlyPermissions(token.permissions)) {
1103
1187
  return false;
1104
1188
  }
1105
- if (!Array.isArray(token.tunnelsGrants) || token.tunnelsGrants.length === 0) {
1189
+ if (token.tunnelsGrants === void 0) {
1106
1190
  return false;
1107
1191
  }
1108
- return token.tunnelsGrants.every((grant) => {
1109
- const tunnels = grant.scopes?.tunnels;
1110
- if (tunnels === void 0) {
1111
- return false;
1192
+ return hasWatchOnlyTunnelGrant(token.tunnelsGrants);
1193
+ }
1194
+ function hasWatchOnlyTunnelGrant(grant) {
1195
+ return watchGrantBranches(grant).every(
1196
+ (branch) => branch.hasList && !branch.hasMutationOrConnect
1197
+ );
1198
+ }
1199
+ function watchGrantBranches(grant) {
1200
+ if ("AND" in grant) {
1201
+ return grant.AND.reduce(
1202
+ (branches, child) => combineWatchGrantBranches(branches, child),
1203
+ [{ hasList: false, hasMutationOrConnect: false }]
1204
+ );
1205
+ }
1206
+ if ("OR" in grant) {
1207
+ return grant.OR.flatMap(watchGrantBranches);
1208
+ }
1209
+ const tunnels = grant.scopes?.tunnels;
1210
+ if (tunnels === void 0) {
1211
+ return [{ hasList: false, hasMutationOrConnect: false }];
1212
+ }
1213
+ return [
1214
+ {
1215
+ hasList: tunnels.list !== void 0,
1216
+ hasMutationOrConnect: tunnels.create !== void 0 || tunnels.connect !== void 0
1112
1217
  }
1113
- return tunnels.list !== void 0 && tunnels.create === void 0 && tunnels.connect === void 0;
1114
- });
1218
+ ];
1219
+ }
1220
+ function combineWatchGrantBranches(branches, child) {
1221
+ return branches.flatMap(
1222
+ (branch) => watchGrantBranches(child).map((childBranch) => ({
1223
+ hasList: branch.hasList || childBranch.hasList,
1224
+ hasMutationOrConnect: branch.hasMutationOrConnect || childBranch.hasMutationOrConnect
1225
+ }))
1226
+ );
1115
1227
  }
1116
1228
  function hasWatchOnlyPermissions(permissions) {
1117
1229
  if (!Array.isArray(permissions)) {
package/dist/openapi.js CHANGED
@@ -103,8 +103,7 @@ var tunnelSchema = z2.object({
103
103
  tls_alpns: z2.array(z2.string()).optional(),
104
104
  tls_min_version: z2.string().optional(),
105
105
  tls_ciphers: z2.array(z2.string()).optional(),
106
- mtls: z2.boolean().optional(),
107
- mtls_ca_cert_pem: z2.string().optional(),
106
+ mtls_auth: z2.boolean().optional(),
108
107
  http_version: z2.enum(["http/1.1", "h2", "h2c", "h3"]).optional(),
109
108
  http_use_tls: z2.boolean().optional(),
110
109
  upstream_tls: z2.boolean().optional(),
@@ -274,7 +273,7 @@ var routedTunnelSchema = z3.object({
274
273
  httpUseTls: z3.boolean().optional(),
275
274
  upstreamTls: z3.boolean().optional(),
276
275
  tokenAuth: z3.boolean().optional(),
277
- mtls: z3.boolean().optional()
276
+ mtls_auth: z3.boolean().optional()
278
277
  });
279
278
  var routingSchema = z3.discriminatedUnion("decision", [
280
279
  z3.object({ decision: z3.literal("routed"), tunnel: routedTunnelSchema }),
@@ -516,7 +515,7 @@ var engineOpenApiDocument = {
516
515
  info: {
517
516
  title: "rstream Engine API",
518
517
  version: "1.0.0",
519
- description: "Operational data-plane API exposed by rstream-engine. This surface is scoped to the authenticated engine or project endpoint and covers live clients, live tunnels, and state-change streams."
518
+ description: "Operational Engine API exposed by rstream engine. This surface is scoped to the authenticated engine or project endpoint and covers live clients, live tunnels, and state-change streams."
520
519
  },
521
520
  servers: [
522
521
  {
package/dist/openapi.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  engineOpenApiDocument,
3
3
  engineOpenApiJson
4
- } from "./chunk-7RZ743EF.mjs";
4
+ } from "./chunk-NHUJ3FYM.mjs";
5
5
  export {
6
6
  engineOpenApiDocument,
7
7
  engineOpenApiJson
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rstreamlabs/tunnels",
3
- "version": "1.0.8",
4
- "description": "JS/TS SDK for the rstream tunnels data plane and engine APIs.",
3
+ "version": "1.0.10",
4
+ "description": "JS/TS SDK for the rstream Engine API and tunnel runtime APIs.",
5
5
  "author": "@uartnet <hello@rstream.io>",
6
6
  "license": "Apache-2.0",
7
7
  "main": "./dist/index.js",
@@ -43,7 +43,7 @@
43
43
  "typescript-config": "*"
44
44
  },
45
45
  "dependencies": {
46
- "@rstreamlabs/rstream": "2.1.1",
46
+ "@rstreamlabs/rstream": "2.1.3",
47
47
  "jsonwebtoken": "^9.0.3",
48
48
  "zod": "^4.3.6"
49
49
  },