@rstreamlabs/tunnels 2.0.0 → 3.1.0
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 +21 -10
- package/dist/index.d.mts +90 -79
- package/dist/index.d.ts +90 -79
- package/dist/index.js +121 -83
- package/dist/index.mjs +121 -83
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -107,7 +107,7 @@ var RstreamAuthResource = class {
|
|
|
107
107
|
if (options?.projectId !== void 0 || options?.workspaceId !== void 0) {
|
|
108
108
|
return options;
|
|
109
109
|
}
|
|
110
|
-
if (
|
|
110
|
+
if (!tunnelResourcesNeedDefaultTarget(tokenParams.resources?.tunnels)) {
|
|
111
111
|
return options ?? {};
|
|
112
112
|
}
|
|
113
113
|
const target = await this.client.getEngineAuthTokenTarget();
|
|
@@ -124,8 +124,8 @@ function createAuthTokenFromClientCredentials(credentials, params, options) {
|
|
|
124
124
|
metadata: {
|
|
125
125
|
engine: options?.engine
|
|
126
126
|
},
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
permissions: null,
|
|
128
|
+
resources: normalizeAuthTokenResources(tokenParams, options)
|
|
129
129
|
};
|
|
130
130
|
const { token } = (0, import_rstream.createClientCredentialsToken)(credentials, {
|
|
131
131
|
claims: payload,
|
|
@@ -133,70 +133,73 @@ function createAuthTokenFromClientCredentials(credentials, params, options) {
|
|
|
133
133
|
});
|
|
134
134
|
return { token };
|
|
135
135
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
136
|
+
function normalizeAuthTokenResources(params, options) {
|
|
137
|
+
return {
|
|
138
|
+
tunnels: applyDefaultTunnelResourceTarget(
|
|
139
|
+
requireTunnelResource(params),
|
|
140
|
+
options
|
|
141
|
+
)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function requireTunnelResource(params) {
|
|
145
|
+
const tunnels = params.resources?.tunnels;
|
|
146
|
+
if (tunnels !== void 0) return tunnels;
|
|
147
|
+
throw new Error("Explicit resources.tunnels is required.");
|
|
145
148
|
}
|
|
146
|
-
function
|
|
149
|
+
function applyDefaultTunnelResourceTarget(resource, options, inheritedTarget = false) {
|
|
147
150
|
if (options?.projectScoped === false) {
|
|
148
|
-
return
|
|
151
|
+
return resource;
|
|
149
152
|
}
|
|
150
|
-
if ("AND" in
|
|
151
|
-
const branchHasTarget = inheritedTarget ||
|
|
153
|
+
if ("AND" in resource) {
|
|
154
|
+
const branchHasTarget = inheritedTarget || resource.AND.some((child) => tunnelResourceTreeHasTarget(child));
|
|
152
155
|
return {
|
|
153
|
-
AND:
|
|
154
|
-
(
|
|
156
|
+
AND: resource.AND.map(
|
|
157
|
+
(child) => applyDefaultTunnelResourceTarget(child, options, branchHasTarget)
|
|
155
158
|
)
|
|
156
159
|
};
|
|
157
160
|
}
|
|
158
|
-
if ("OR" in
|
|
161
|
+
if ("OR" in resource) {
|
|
159
162
|
return {
|
|
160
|
-
OR:
|
|
161
|
-
(
|
|
163
|
+
OR: resource.OR.map(
|
|
164
|
+
(child) => applyDefaultTunnelResourceTarget(child, options, inheritedTarget)
|
|
162
165
|
)
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
|
-
if (
|
|
166
|
-
return
|
|
168
|
+
if (resource.scopes === void 0 || tunnelResourceHasTarget(resource) || inheritedTarget) {
|
|
169
|
+
return resource;
|
|
167
170
|
}
|
|
168
|
-
return { ...
|
|
171
|
+
return { ...normalizeTunnelResourceTarget(options), scopes: resource.scopes };
|
|
169
172
|
}
|
|
170
|
-
function
|
|
171
|
-
if (
|
|
173
|
+
function tunnelResourcesNeedDefaultTarget(resource, inheritedTarget = false) {
|
|
174
|
+
if (resource === void 0) {
|
|
172
175
|
return false;
|
|
173
176
|
}
|
|
174
|
-
if ("AND" in
|
|
175
|
-
const branchHasTarget = inheritedTarget ||
|
|
176
|
-
return
|
|
177
|
-
(
|
|
177
|
+
if ("AND" in resource) {
|
|
178
|
+
const branchHasTarget = inheritedTarget || resource.AND.some((child) => tunnelResourceTreeHasTarget(child));
|
|
179
|
+
return resource.AND.some(
|
|
180
|
+
(child) => tunnelResourcesNeedDefaultTarget(child, branchHasTarget)
|
|
178
181
|
);
|
|
179
182
|
}
|
|
180
|
-
if ("OR" in
|
|
181
|
-
return
|
|
182
|
-
(
|
|
183
|
+
if ("OR" in resource) {
|
|
184
|
+
return resource.OR.some(
|
|
185
|
+
(child) => tunnelResourcesNeedDefaultTarget(child, inheritedTarget)
|
|
183
186
|
);
|
|
184
187
|
}
|
|
185
|
-
return
|
|
188
|
+
return resource.scopes !== void 0 && !tunnelResourceHasTarget(resource) && !inheritedTarget;
|
|
186
189
|
}
|
|
187
|
-
function
|
|
188
|
-
return
|
|
190
|
+
function tunnelResourceHasTarget(resource) {
|
|
191
|
+
return resource.projects !== void 0 || resource.workspaces !== void 0;
|
|
189
192
|
}
|
|
190
|
-
function
|
|
191
|
-
if ("AND" in
|
|
192
|
-
return
|
|
193
|
+
function tunnelResourceTreeHasTarget(resource) {
|
|
194
|
+
if ("AND" in resource) {
|
|
195
|
+
return resource.AND.some((child) => tunnelResourceTreeHasTarget(child));
|
|
193
196
|
}
|
|
194
|
-
if ("OR" in
|
|
195
|
-
return
|
|
197
|
+
if ("OR" in resource) {
|
|
198
|
+
return resource.OR.some((child) => tunnelResourceTreeHasTarget(child));
|
|
196
199
|
}
|
|
197
|
-
return
|
|
200
|
+
return tunnelResourceHasTarget(resource);
|
|
198
201
|
}
|
|
199
|
-
function
|
|
202
|
+
function normalizeTunnelResourceTarget(options) {
|
|
200
203
|
const projectId = normalizeOptionalString(options?.projectId);
|
|
201
204
|
const workspaceId = normalizeOptionalString(options?.workspaceId);
|
|
202
205
|
if (projectId !== void 0 && workspaceId !== void 0) {
|
|
@@ -212,7 +215,7 @@ function normalizeTunnelGrantTarget(options) {
|
|
|
212
215
|
return {};
|
|
213
216
|
}
|
|
214
217
|
throw new Error(
|
|
215
|
-
"Project ID or workspace ID is required when using scope-only tunnel
|
|
218
|
+
"Project ID or workspace ID is required when using scope-only tunnel resources."
|
|
216
219
|
);
|
|
217
220
|
}
|
|
218
221
|
function normalizeOptionalString(value) {
|
|
@@ -1129,6 +1132,9 @@ function normalizeTTLSeconds(ttlSeconds) {
|
|
|
1129
1132
|
}
|
|
1130
1133
|
return ttl;
|
|
1131
1134
|
}
|
|
1135
|
+
function createAPITURNCredentialsParams(ttlSeconds) {
|
|
1136
|
+
return ttlSeconds === void 0 ? {} : { ttlSeconds: normalizeTTLSeconds(ttlSeconds) };
|
|
1137
|
+
}
|
|
1132
1138
|
function normalizePort(port, fallback, name) {
|
|
1133
1139
|
const normalized = port ?? fallback;
|
|
1134
1140
|
if (!Number.isInteger(normalized) || normalized < 1 || normalized > 65535) {
|
|
@@ -1158,19 +1164,21 @@ function parseTURNTokenClaims(token) {
|
|
|
1158
1164
|
if (!payloadPart) {
|
|
1159
1165
|
throw new Error("Invalid token format.");
|
|
1160
1166
|
}
|
|
1161
|
-
|
|
1162
|
-
try {
|
|
1163
|
-
const payload = Buffer.from(payloadPart, "base64url").toString("utf8");
|
|
1164
|
-
decoded = JSON.parse(payload);
|
|
1165
|
-
} catch {
|
|
1166
|
-
throw new Error("Invalid token format.");
|
|
1167
|
-
}
|
|
1167
|
+
const decoded = parseTURNTokenPayload(payloadPart);
|
|
1168
1168
|
const parsed = turnTokenClaimsSchema.safeParse(decoded);
|
|
1169
1169
|
if (!parsed.success) {
|
|
1170
1170
|
throw new Error("Invalid token format.");
|
|
1171
1171
|
}
|
|
1172
1172
|
return parsed.data;
|
|
1173
1173
|
}
|
|
1174
|
+
function parseTURNTokenPayload(payloadPart) {
|
|
1175
|
+
try {
|
|
1176
|
+
const payload = Buffer.from(payloadPart, "base64url").toString("utf8");
|
|
1177
|
+
return JSON.parse(payload);
|
|
1178
|
+
} catch {
|
|
1179
|
+
throw new Error("Invalid token format.");
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1174
1182
|
function requirePATExpiration(claims, now) {
|
|
1175
1183
|
const exp = claims.exp;
|
|
1176
1184
|
if (typeof exp !== "number") {
|
|
@@ -1244,8 +1252,12 @@ async function createAPITURNCredentials(options) {
|
|
|
1244
1252
|
apiUrl: options.apiUrl,
|
|
1245
1253
|
credentials: options.credentials
|
|
1246
1254
|
});
|
|
1255
|
+
const params = createAPITURNCredentialsParams(options.ttlSeconds);
|
|
1247
1256
|
if (projectId) {
|
|
1248
|
-
return await client.tunnels.projects.createTurnCredentials(
|
|
1257
|
+
return await client.tunnels.projects.createTurnCredentials(
|
|
1258
|
+
projectId,
|
|
1259
|
+
params
|
|
1260
|
+
);
|
|
1249
1261
|
}
|
|
1250
1262
|
if (!projectEndpoint) {
|
|
1251
1263
|
throw new Error(
|
|
@@ -1253,7 +1265,8 @@ async function createAPITURNCredentials(options) {
|
|
|
1253
1265
|
);
|
|
1254
1266
|
}
|
|
1255
1267
|
return await client.tunnels.projects.createTurnCredentialsByEndpoint(
|
|
1256
|
-
projectEndpoint
|
|
1268
|
+
projectEndpoint,
|
|
1269
|
+
params
|
|
1257
1270
|
);
|
|
1258
1271
|
}
|
|
1259
1272
|
function createPATTURNCredentials(options) {
|
|
@@ -1371,7 +1384,8 @@ async function createTURNCredentials(options) {
|
|
|
1371
1384
|
apiUrl: options.apiUrl,
|
|
1372
1385
|
credentials: controlPlaneCredentials,
|
|
1373
1386
|
projectEndpoint,
|
|
1374
|
-
projectId: options.projectId
|
|
1387
|
+
projectId: options.projectId,
|
|
1388
|
+
ttlSeconds: options.ttlSeconds
|
|
1375
1389
|
});
|
|
1376
1390
|
}
|
|
1377
1391
|
const clusterDomain = normalizeClusterDomain(options.clusterDomain) ?? (projectEndpoint && options.engine ? deriveClusterDomainFromEngine(projectEndpoint, options.engine) : void 0);
|
|
@@ -1607,9 +1621,11 @@ var RstreamTunnelsClient = class {
|
|
|
1607
1621
|
async getEngineAuthTokenParams() {
|
|
1608
1622
|
const target = await this.getEngineAuthTokenTarget();
|
|
1609
1623
|
return {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1624
|
+
resources: {
|
|
1625
|
+
tunnels: {
|
|
1626
|
+
...target,
|
|
1627
|
+
scopes: engineAuthTokenScopes
|
|
1628
|
+
}
|
|
1613
1629
|
}
|
|
1614
1630
|
};
|
|
1615
1631
|
}
|
|
@@ -1719,37 +1735,38 @@ function validateWatchQueryToken(token) {
|
|
|
1719
1735
|
"Watch requires a token with a bounded lifetime of at most 3600 seconds."
|
|
1720
1736
|
);
|
|
1721
1737
|
}
|
|
1722
|
-
if (!
|
|
1738
|
+
if (!hasWatchOnlyTunnelResources(parsed.data)) {
|
|
1723
1739
|
throw new Error(
|
|
1724
|
-
"Watch requires a fine-grained token limited to tunnel list
|
|
1740
|
+
"Watch requires a fine-grained token limited to tunnel list resources for URL-based authentication."
|
|
1725
1741
|
);
|
|
1726
1742
|
}
|
|
1727
1743
|
}
|
|
1728
|
-
function
|
|
1744
|
+
function hasWatchOnlyTunnelResources(token) {
|
|
1729
1745
|
if (!hasWatchOnlyPermissions(token.permissions)) {
|
|
1730
1746
|
return false;
|
|
1731
1747
|
}
|
|
1732
|
-
|
|
1748
|
+
const resource = token.resources?.tunnels;
|
|
1749
|
+
if (resource === void 0) {
|
|
1733
1750
|
return false;
|
|
1734
1751
|
}
|
|
1735
|
-
return
|
|
1752
|
+
return hasWatchOnlyTunnelResource(resource);
|
|
1736
1753
|
}
|
|
1737
|
-
function
|
|
1738
|
-
return
|
|
1754
|
+
function hasWatchOnlyTunnelResource(resource) {
|
|
1755
|
+
return watchResourceBranches(resource).every(
|
|
1739
1756
|
(branch) => branch.hasList && !branch.hasMutationOrConnect
|
|
1740
1757
|
);
|
|
1741
1758
|
}
|
|
1742
|
-
function
|
|
1743
|
-
if ("AND" in
|
|
1744
|
-
return
|
|
1745
|
-
(branches, child) =>
|
|
1759
|
+
function watchResourceBranches(resource) {
|
|
1760
|
+
if ("AND" in resource) {
|
|
1761
|
+
return resource.AND.reduce(
|
|
1762
|
+
(branches, child) => combineWatchResourceBranches(branches, child),
|
|
1746
1763
|
[{ hasList: false, hasMutationOrConnect: false }]
|
|
1747
1764
|
);
|
|
1748
1765
|
}
|
|
1749
|
-
if ("OR" in
|
|
1750
|
-
return
|
|
1766
|
+
if ("OR" in resource) {
|
|
1767
|
+
return resource.OR.flatMap(watchResourceBranches);
|
|
1751
1768
|
}
|
|
1752
|
-
const tunnels =
|
|
1769
|
+
const tunnels = resource.scopes?.tunnels;
|
|
1753
1770
|
if (tunnels === void 0) {
|
|
1754
1771
|
return [{ hasList: false, hasMutationOrConnect: false }];
|
|
1755
1772
|
}
|
|
@@ -1760,9 +1777,9 @@ function watchGrantBranches(grant) {
|
|
|
1760
1777
|
}
|
|
1761
1778
|
];
|
|
1762
1779
|
}
|
|
1763
|
-
function
|
|
1780
|
+
function combineWatchResourceBranches(branches, child) {
|
|
1764
1781
|
return branches.flatMap(
|
|
1765
|
-
(branch) =>
|
|
1782
|
+
(branch) => watchResourceBranches(child).map((childBranch) => ({
|
|
1766
1783
|
hasList: branch.hasList || childBranch.hasList,
|
|
1767
1784
|
hasMutationOrConnect: branch.hasMutationOrConnect || childBranch.hasMutationOrConnect
|
|
1768
1785
|
}))
|
|
@@ -1773,7 +1790,7 @@ function hasWatchOnlyPermissions(permissions) {
|
|
|
1773
1790
|
return true;
|
|
1774
1791
|
}
|
|
1775
1792
|
return permissions.every(
|
|
1776
|
-
(permission) => permission === "tunnels.resources.read-only"
|
|
1793
|
+
(permission) => permission === "tunnels.resources.read-only"
|
|
1777
1794
|
);
|
|
1778
1795
|
}
|
|
1779
1796
|
var Watch = class {
|
|
@@ -1859,34 +1876,50 @@ var Watch = class {
|
|
|
1859
1876
|
|
|
1860
1877
|
// src/webtty.ts
|
|
1861
1878
|
var z7 = __toESM(require("zod"));
|
|
1862
|
-
var
|
|
1879
|
+
var osFamilySchema = z7.enum([
|
|
1863
1880
|
"linux",
|
|
1864
1881
|
"macos",
|
|
1865
1882
|
"windows",
|
|
1866
1883
|
"netbsd",
|
|
1867
1884
|
"openbsd",
|
|
1868
1885
|
"freebsd"
|
|
1869
|
-
];
|
|
1886
|
+
]);
|
|
1887
|
+
var webttyCapabilitySchema = z7.enum(["exec", "fs"]);
|
|
1888
|
+
var webttyFSModeSchema = z7.enum(["read-write", "read-only"]);
|
|
1870
1889
|
var webttyServerSchema = z7.object({
|
|
1871
1890
|
tunnel_id: z7.string(),
|
|
1872
1891
|
host: z7.string(),
|
|
1873
1892
|
token_auth: z7.boolean(),
|
|
1874
|
-
|
|
1893
|
+
capabilities: z7.array(webttyCapabilitySchema).optional(),
|
|
1894
|
+
exec_path: z7.string().optional(),
|
|
1895
|
+
fs_path: z7.string().optional(),
|
|
1896
|
+
fs_mode: webttyFSModeSchema.optional(),
|
|
1897
|
+
os_family: osFamilySchema.optional(),
|
|
1875
1898
|
arch: z7.string().optional(),
|
|
1876
1899
|
os_id: z7.string().optional(),
|
|
1877
|
-
// /etc/os-release::ID (ubuntu, debian, rocky, etc.)
|
|
1878
1900
|
os_version_id: z7.string().optional(),
|
|
1879
|
-
// /etc/os-release::VERSION_ID (24.04, 22.04, 11, 10.0.19045, ...)
|
|
1880
1901
|
os_version_codename: z7.string().optional(),
|
|
1881
|
-
// /etc/os-release::VERSION_CODENAME (jammy, noble...)
|
|
1882
1902
|
os_pretty_name: z7.string().optional(),
|
|
1883
|
-
// /etc/os-release::PRETTY_NAME
|
|
1884
1903
|
kernel_release: z7.string().optional(),
|
|
1885
|
-
// uname -r
|
|
1886
1904
|
hostname: z7.string().optional(),
|
|
1887
|
-
// uname -n
|
|
1888
1905
|
labels: z7.record(z7.string(), z7.string().optional()).optional()
|
|
1889
1906
|
});
|
|
1907
|
+
function isWebTTYCapability(value) {
|
|
1908
|
+
return webttyCapabilitySchema.safeParse(value).success;
|
|
1909
|
+
}
|
|
1910
|
+
function parseWebTTYCapabilities(value) {
|
|
1911
|
+
const values = (value ?? "").split(",").map((capability) => capability.trim()).filter(isWebTTYCapability);
|
|
1912
|
+
return webttyCapabilitySchema.options.filter(
|
|
1913
|
+
(capability) => values.includes(capability)
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
function webTTYCapabilitiesForLabels(labels) {
|
|
1917
|
+
const parsed = parseWebTTYCapabilities(labels["rstream.webtty.capabilities"]);
|
|
1918
|
+
return parsed.length > 0 ? parsed : ["exec"];
|
|
1919
|
+
}
|
|
1920
|
+
function webTTYHasCapability(capabilities, capability) {
|
|
1921
|
+
return capabilities.includes(capability);
|
|
1922
|
+
}
|
|
1890
1923
|
function parser(tunnel) {
|
|
1891
1924
|
if (tunnel.status !== "online") return null;
|
|
1892
1925
|
if (tunnel.publish !== true) return null;
|
|
@@ -1895,6 +1928,7 @@ function parser(tunnel) {
|
|
|
1895
1928
|
if (tunnelLabels["application-protocol"] !== "rstream.webtty") {
|
|
1896
1929
|
return null;
|
|
1897
1930
|
}
|
|
1931
|
+
const capabilities = webTTYCapabilitiesForLabels(tunnelLabels);
|
|
1898
1932
|
const labels = {};
|
|
1899
1933
|
for (const [key, value] of Object.entries(tunnelLabels)) {
|
|
1900
1934
|
if (!key.startsWith("rstream.webtty.label.")) continue;
|
|
@@ -1906,6 +1940,10 @@ function parser(tunnel) {
|
|
|
1906
1940
|
tunnel_id: tunnel.id,
|
|
1907
1941
|
host: formatTunnelHost(tunnel),
|
|
1908
1942
|
token_auth: tunnel.token_auth === true,
|
|
1943
|
+
capabilities,
|
|
1944
|
+
exec_path: webTTYHasCapability(capabilities, "exec") ? tunnelLabels["rstream.webtty.exec.path"] ?? "/" : void 0,
|
|
1945
|
+
fs_path: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.path"] ?? "/fs" : void 0,
|
|
1946
|
+
fs_mode: webTTYHasCapability(capabilities, "fs") ? tunnelLabels["rstream.webtty.fs.mode"] ?? "read-write" : void 0,
|
|
1909
1947
|
os_family: tunnelLabels["rstream.webtty.os_family"],
|
|
1910
1948
|
arch: tunnelLabels["rstream.webtty.arch"],
|
|
1911
1949
|
os_id: tunnelLabels["rstream.webtty.os_id"],
|