@cmmd-center/forge 0.9.2 → 0.9.3
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/bin.cjs +124 -20
- package/dist/bin.mjs +124 -20
- package/package.json +3 -3
package/dist/bin.cjs
CHANGED
|
@@ -32042,6 +32042,25 @@ function readHiddenLine(input) {
|
|
|
32042
32042
|
});
|
|
32043
32043
|
}
|
|
32044
32044
|
//#endregion
|
|
32045
|
+
//#region src/connect/loginRedirect.ts
|
|
32046
|
+
/**
|
|
32047
|
+
* The OAuth redirect URI for `connect login --email`.
|
|
32048
|
+
*
|
|
32049
|
+
* This names where the *client* lives, not where CMMD lives. Sending CMMD's own
|
|
32050
|
+
* origin is rejected with a 400 — which is exactly how password login first
|
|
32051
|
+
* shipped broken.
|
|
32052
|
+
*
|
|
32053
|
+
* The exchange never actually fetches this URL: both legs run in-process and
|
|
32054
|
+
* the authorization code is read off the redirect's Location header. It only
|
|
32055
|
+
* has to match a URI CMMD accepts. CMMD follows the usual native-app rule of
|
|
32056
|
+
* matching loopback redirects on any port, so the configured port is sent
|
|
32057
|
+
* as-is, keeping the CLI consistent with what the browser flow would use.
|
|
32058
|
+
*/
|
|
32059
|
+
const DEFAULT_PORT$1 = 3e3;
|
|
32060
|
+
function resolveLoginRedirectUri(input) {
|
|
32061
|
+
return `http://localhost:${typeof input.port === "number" && Number.isInteger(input.port) && input.port > 0 && input.port <= 65535 ? input.port : DEFAULT_PORT$1}/api/auth/callback`;
|
|
32062
|
+
}
|
|
32063
|
+
//#endregion
|
|
32045
32064
|
//#region src/connect/passwordLogin.ts
|
|
32046
32065
|
async function runPasswordLogin(deps) {
|
|
32047
32066
|
const email = deps.email.trim();
|
|
@@ -63823,7 +63842,7 @@ function normalizeNumberish(value) {
|
|
|
63823
63842
|
}
|
|
63824
63843
|
//#endregion
|
|
63825
63844
|
//#region package.json
|
|
63826
|
-
var version$1 = "0.9.
|
|
63845
|
+
var version$1 = "0.9.3";
|
|
63827
63846
|
//#endregion
|
|
63828
63847
|
//#region src/sentry.ts
|
|
63829
63848
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -66398,11 +66417,14 @@ function resolveConnectDeviceIdentity(identityPath, options) {
|
|
|
66398
66417
|
function resolveConnectRelayConfig(stateDir) {
|
|
66399
66418
|
if (!readConnectState(node_path.join(stateDir, "connect.json")).linked) return {
|
|
66400
66419
|
linked: false,
|
|
66401
|
-
bridgeToken: void 0
|
|
66420
|
+
bridgeToken: void 0,
|
|
66421
|
+
runtimeId: void 0
|
|
66402
66422
|
};
|
|
66423
|
+
const secrets = resolveConnectDeviceSecrets(node_path.join(stateDir, "connect-device.json"));
|
|
66403
66424
|
return {
|
|
66404
66425
|
linked: true,
|
|
66405
|
-
bridgeToken:
|
|
66426
|
+
bridgeToken: secrets.bridgeToken,
|
|
66427
|
+
runtimeId: secrets.runtimeId
|
|
66406
66428
|
};
|
|
66407
66429
|
}
|
|
66408
66430
|
//#endregion
|
|
@@ -66568,6 +66590,7 @@ var ServerConfig = class ServerConfig extends require_Schema$1.Service()("forge/
|
|
|
66568
66590
|
desktopBootstrapToken: void 0,
|
|
66569
66591
|
desktopRelayBridgeToken: void 0,
|
|
66570
66592
|
connectRelayBridgeToken: void 0,
|
|
66593
|
+
connectRelayRuntimeId: void 0,
|
|
66571
66594
|
connectRelayLinked: false,
|
|
66572
66595
|
connectDisplayName: "forge",
|
|
66573
66596
|
connectPlatform: "linux",
|
|
@@ -68733,6 +68756,20 @@ function registerProxyRoute(route) {
|
|
|
68733
68756
|
return add$1(route.method, route.forgePath, handler);
|
|
68734
68757
|
}
|
|
68735
68758
|
//#endregion
|
|
68759
|
+
//#region src/connect/credentialRefresh.ts
|
|
68760
|
+
async function refreshConnectCredential(deps) {
|
|
68761
|
+
if (deps.refreshToken.length === 0) return null;
|
|
68762
|
+
try {
|
|
68763
|
+
return toConnectCredential(await deps.postJson(`${deps.commandAppUrl}/api/mcp-server/oauth/token`, {
|
|
68764
|
+
grant_type: "refresh_token",
|
|
68765
|
+
refresh_token: deps.refreshToken,
|
|
68766
|
+
client_id: deps.clientId
|
|
68767
|
+
}));
|
|
68768
|
+
} catch {
|
|
68769
|
+
return null;
|
|
68770
|
+
}
|
|
68771
|
+
}
|
|
68772
|
+
//#endregion
|
|
68736
68773
|
//#region src/relay/localBridge.ts
|
|
68737
68774
|
const DESKTOP_RELAY_CONTEXT_HEADER = "x-forge-relay-context";
|
|
68738
68775
|
const decodeRelayContext = require_Schema$1.decodeUnknownSync(RelayLocalBridgeContext);
|
|
@@ -69006,6 +69043,30 @@ function makeRelayDeliveryMiddlewareLayer(context, inbox, reconcileInterruptedCo
|
|
|
69006
69043
|
});
|
|
69007
69044
|
}
|
|
69008
69045
|
//#endregion
|
|
69046
|
+
//#region src/relay/bridgeAccess.ts
|
|
69047
|
+
function relayBridgeAccessAllowed(input) {
|
|
69048
|
+
if (!input.isLoopback) return false;
|
|
69049
|
+
if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
|
|
69050
|
+
return input.mode === "desktop" || input.connectLinked;
|
|
69051
|
+
}
|
|
69052
|
+
function resolveAuthorizedBridgeRuntimeId(input) {
|
|
69053
|
+
const isDesktop = input.mode === "desktop";
|
|
69054
|
+
if (!relayBridgeAccessAllowed({
|
|
69055
|
+
mode: input.mode,
|
|
69056
|
+
connectLinked: input.connectLinked,
|
|
69057
|
+
isLoopback: input.isLoopback,
|
|
69058
|
+
suppliedToken: input.suppliedToken,
|
|
69059
|
+
expectedToken: isDesktop ? input.desktopToken : input.connectToken
|
|
69060
|
+
})) return null;
|
|
69061
|
+
return (isDesktop ? input.desktopRuntimeId : input.connectRuntimeId) ?? null;
|
|
69062
|
+
}
|
|
69063
|
+
//#endregion
|
|
69064
|
+
//#region src/relay/controlPlaneAuthority.ts
|
|
69065
|
+
function selectControlPlaneAuthority(input) {
|
|
69066
|
+
if (input.mode === "desktop") return "session";
|
|
69067
|
+
return input.connectLinked ? "credential" : null;
|
|
69068
|
+
}
|
|
69069
|
+
//#endregion
|
|
69009
69070
|
//#region src/desktop/relayOwnerSession.ts
|
|
69010
69071
|
var DesktopRelayOwnerSessionError = class extends require_Schema$1.TaggedError("DesktopRelayOwnerSessionError") {};
|
|
69011
69072
|
const resolveDesktopRelayOwnerSession = (request) => require_Schema$1.gen(function* () {
|
|
@@ -69101,6 +69162,8 @@ function relayControlPlaneBodyMatchesRuntime(path, body, configuredRuntimeId) {
|
|
|
69101
69162
|
if (!isRecord$17(body) || !isRecord$17(body.runtime)) return false;
|
|
69102
69163
|
return body.runtime.runtimeId === configuredRuntimeId;
|
|
69103
69164
|
}
|
|
69165
|
+
/** Matches the client id `connect login` used, so CMMD accepts the refresh. */
|
|
69166
|
+
const CONNECT_CONTROL_PLANE_CLIENT_ID = "cmmd-forge-web";
|
|
69104
69167
|
function errorResponse(status, code, error) {
|
|
69105
69168
|
return require_HttpServer.jsonUnsafe({
|
|
69106
69169
|
error,
|
|
@@ -69119,9 +69182,16 @@ function relayProxyErrorResponse(error) {
|
|
|
69119
69182
|
return buildProxyErrorResponse(error);
|
|
69120
69183
|
}
|
|
69121
69184
|
function authorizedBridgeRuntimeId(request, config) {
|
|
69122
|
-
|
|
69123
|
-
|
|
69124
|
-
|
|
69185
|
+
return resolveAuthorizedBridgeRuntimeId({
|
|
69186
|
+
mode: config.mode === "desktop" ? "desktop" : "web",
|
|
69187
|
+
connectLinked: config.connectRelayLinked,
|
|
69188
|
+
isLoopback: isLoopbackRelayRequest(request),
|
|
69189
|
+
suppliedToken: request.headers[DESKTOP_RELAY_BRIDGE_TOKEN_HEADER],
|
|
69190
|
+
desktopToken: config.desktopRelayBridgeToken,
|
|
69191
|
+
connectToken: config.connectRelayBridgeToken,
|
|
69192
|
+
desktopRuntimeId: config.desktopRelayRuntimeId,
|
|
69193
|
+
connectRuntimeId: config.connectRelayRuntimeId
|
|
69194
|
+
});
|
|
69125
69195
|
}
|
|
69126
69196
|
function validateBridgeOperation(request, runtimeId) {
|
|
69127
69197
|
if (request.runtimeId !== runtimeId) return {
|
|
@@ -69148,9 +69218,10 @@ function validateBridgeOperation(request, runtimeId) {
|
|
|
69148
69218
|
}
|
|
69149
69219
|
function dispatchRelayControlPlaneOperation(input) {
|
|
69150
69220
|
return require_Schema$1.gen(function* () {
|
|
69151
|
-
const
|
|
69152
|
-
|
|
69153
|
-
|
|
69221
|
+
const caller = input.caller;
|
|
69222
|
+
const resolveJwt = (forceRefresh = false) => caller.kind === "credential" ? forceRefresh ? require_Schema$1.promise(() => caller.renew()) : require_Schema$1.succeed(caller.accessToken) : resolveCmmdJwtForSession({
|
|
69223
|
+
sessionId: caller.sessionId,
|
|
69224
|
+
subject: caller.subject,
|
|
69154
69225
|
logPrefix: "desktop-relay-control-plane",
|
|
69155
69226
|
forceRefresh
|
|
69156
69227
|
});
|
|
@@ -69172,19 +69243,57 @@ function dispatchRelayControlPlaneOperation(input) {
|
|
|
69172
69243
|
}
|
|
69173
69244
|
const desktopRelayControlPlaneRouteLayer = add$1("POST", DESKTOP_RELAY_CONTROL_PLANE_PATH, require_Schema$1.gen(function* () {
|
|
69174
69245
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
69175
|
-
const
|
|
69246
|
+
const config = yield* ServerConfig;
|
|
69247
|
+
const runtimeId = authorizedBridgeRuntimeId(request, config);
|
|
69176
69248
|
if (!runtimeId) return forbiddenResponse();
|
|
69177
69249
|
const decoded = yield* require_HttpServer.schemaBodyJson(DesktopRelayControlPlaneRequest).pipe(require_Schema$1.result);
|
|
69178
69250
|
if (require_Schema$1.isFailure$1(decoded)) return errorResponse(400, "RELAY_BRIDGE_REQUEST_INVALID", "Invalid relay bridge request.");
|
|
69179
69251
|
const operation = validateBridgeOperation(decoded.success, runtimeId);
|
|
69180
69252
|
if (!operation.ok) return operation.response;
|
|
69253
|
+
const authority = selectControlPlaneAuthority({
|
|
69254
|
+
mode: config.mode === "desktop" ? "desktop" : "web",
|
|
69255
|
+
connectLinked: config.connectRelayLinked
|
|
69256
|
+
});
|
|
69257
|
+
if (authority === null) return forbiddenResponse();
|
|
69258
|
+
if (authority === "credential") {
|
|
69259
|
+
const credential = readConnectCredential(node_path.join(config.stateDir, "connect-credential.json"), createFileSecretStorage({ keyPath: node_path.join(config.stateDir, "connect-secret.key") }));
|
|
69260
|
+
if (!credential) return errorResponse(401, "CMMD_AUTH_REQUIRED", "This machine is not signed in to CMMD. Run `cmmd-forge connect login`.");
|
|
69261
|
+
const credentialPath = node_path.join(config.stateDir, "connect-credential.json");
|
|
69262
|
+
const secrets = createFileSecretStorage({ keyPath: node_path.join(config.stateDir, "connect-secret.key") });
|
|
69263
|
+
return yield* dispatchRelayControlPlaneOperation({
|
|
69264
|
+
caller: {
|
|
69265
|
+
kind: "credential",
|
|
69266
|
+
accessToken: credential.accessToken,
|
|
69267
|
+
renew: async () => {
|
|
69268
|
+
const renewed = await refreshConnectCredential({
|
|
69269
|
+
commandAppUrl: resolveCommandAppUrl(),
|
|
69270
|
+
clientId: CONNECT_CONTROL_PLANE_CLIENT_ID,
|
|
69271
|
+
refreshToken: credential.refreshToken,
|
|
69272
|
+
postJson: async (url, body) => await (await fetch(url, {
|
|
69273
|
+
method: "POST",
|
|
69274
|
+
headers: { "content-type": "application/json" },
|
|
69275
|
+
body: JSON.stringify(body)
|
|
69276
|
+
})).json()
|
|
69277
|
+
});
|
|
69278
|
+
if (!renewed) return null;
|
|
69279
|
+
writeConnectCredential(credentialPath, secrets, renewed);
|
|
69280
|
+
return renewed.accessToken;
|
|
69281
|
+
}
|
|
69282
|
+
},
|
|
69283
|
+
path: operation.path,
|
|
69284
|
+
body: operation.body
|
|
69285
|
+
});
|
|
69286
|
+
}
|
|
69181
69287
|
const ownerResult = yield* resolveDesktopRelayOwnerSession(request).pipe(require_Schema$1.result);
|
|
69182
69288
|
if (require_Schema$1.isFailure$1(ownerResult)) {
|
|
69183
69289
|
const error = ownerResult.failure;
|
|
69184
69290
|
return errorResponse(error.status, error.code, error.message);
|
|
69185
69291
|
}
|
|
69186
69292
|
return yield* dispatchRelayControlPlaneOperation({
|
|
69187
|
-
|
|
69293
|
+
caller: {
|
|
69294
|
+
kind: "session",
|
|
69295
|
+
...ownerResult.success
|
|
69296
|
+
},
|
|
69188
69297
|
path: operation.path,
|
|
69189
69298
|
body: operation.body
|
|
69190
69299
|
});
|
|
@@ -97901,13 +98010,6 @@ async function reconcileCmmdWorkRunPrMerged(deps, input) {
|
|
|
97901
98010
|
};
|
|
97902
98011
|
}
|
|
97903
98012
|
//#endregion
|
|
97904
|
-
//#region src/relay/bridgeAccess.ts
|
|
97905
|
-
function relayBridgeAccessAllowed(input) {
|
|
97906
|
-
if (!input.isLoopback) return false;
|
|
97907
|
-
if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
|
|
97908
|
-
return input.mode === "desktop" || input.connectLinked;
|
|
97909
|
-
}
|
|
97910
|
-
//#endregion
|
|
97911
98013
|
//#region ../../packages/shared/src/sessionScopes.ts
|
|
97912
98014
|
/**
|
|
97913
98015
|
* Which RPC scopes a direct-WebSocket session holds, by its role.
|
|
@@ -307752,6 +307854,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => require_Schema$1.ge
|
|
|
307752
307854
|
desktopBootstrapToken,
|
|
307753
307855
|
desktopRelayBridgeToken,
|
|
307754
307856
|
connectRelayBridgeToken: connectRelay.bridgeToken,
|
|
307857
|
+
connectRelayRuntimeId: connectRelay.runtimeId,
|
|
307755
307858
|
connectRelayLinked: connectRelay.linked,
|
|
307756
307859
|
connectDisplayName: node_os.hostname(),
|
|
307757
307860
|
connectPlatform,
|
|
@@ -308171,7 +308274,8 @@ const connectLoginCommand = make$32("login", {
|
|
|
308171
308274
|
noBrowser: noBrowserLoginFlag,
|
|
308172
308275
|
email: loginEmailFlag
|
|
308173
308276
|
}).pipe(withDescription$1("Sign this machine in to CMMD so it can be offered over the relay."), withHandler((flags) => require_Schema$1.gen(function* () {
|
|
308174
|
-
const
|
|
308277
|
+
const config = yield* resolveCliAuthConfig(flags, yield* LogLevel);
|
|
308278
|
+
const stateDir = config.stateDir;
|
|
308175
308279
|
const startedAt = Date.now();
|
|
308176
308280
|
const emailOption = flags.email;
|
|
308177
308281
|
if (require_Schema$1.isSome(emailOption)) {
|
|
@@ -308186,7 +308290,7 @@ const connectLoginCommand = make$32("login", {
|
|
|
308186
308290
|
exchange: (email, secret) => require_Schema$1.runPromise(exchangeCmmdPasswordForOAuthToken({
|
|
308187
308291
|
email,
|
|
308188
308292
|
password: secret,
|
|
308189
|
-
redirectUri:
|
|
308293
|
+
redirectUri: resolveLoginRedirectUri({ port: config.port })
|
|
308190
308294
|
})).catch((cause) => {
|
|
308191
308295
|
if (isRejectedCredential(cause)) return {};
|
|
308192
308296
|
throw cause;
|
package/dist/bin.mjs
CHANGED
|
@@ -31816,6 +31816,25 @@ function readHiddenLine(input) {
|
|
|
31816
31816
|
});
|
|
31817
31817
|
}
|
|
31818
31818
|
//#endregion
|
|
31819
|
+
//#region src/connect/loginRedirect.ts
|
|
31820
|
+
/**
|
|
31821
|
+
* The OAuth redirect URI for `connect login --email`.
|
|
31822
|
+
*
|
|
31823
|
+
* This names where the *client* lives, not where CMMD lives. Sending CMMD's own
|
|
31824
|
+
* origin is rejected with a 400 — which is exactly how password login first
|
|
31825
|
+
* shipped broken.
|
|
31826
|
+
*
|
|
31827
|
+
* The exchange never actually fetches this URL: both legs run in-process and
|
|
31828
|
+
* the authorization code is read off the redirect's Location header. It only
|
|
31829
|
+
* has to match a URI CMMD accepts. CMMD follows the usual native-app rule of
|
|
31830
|
+
* matching loopback redirects on any port, so the configured port is sent
|
|
31831
|
+
* as-is, keeping the CLI consistent with what the browser flow would use.
|
|
31832
|
+
*/
|
|
31833
|
+
const DEFAULT_PORT$1 = 3e3;
|
|
31834
|
+
function resolveLoginRedirectUri(input) {
|
|
31835
|
+
return `http://localhost:${typeof input.port === "number" && Number.isInteger(input.port) && input.port > 0 && input.port <= 65535 ? input.port : DEFAULT_PORT$1}/api/auth/callback`;
|
|
31836
|
+
}
|
|
31837
|
+
//#endregion
|
|
31819
31838
|
//#region src/connect/passwordLogin.ts
|
|
31820
31839
|
async function runPasswordLogin(deps) {
|
|
31821
31840
|
const email = deps.email.trim();
|
|
@@ -63597,7 +63616,7 @@ function normalizeNumberish(value) {
|
|
|
63597
63616
|
}
|
|
63598
63617
|
//#endregion
|
|
63599
63618
|
//#region package.json
|
|
63600
|
-
var version$1 = "0.9.
|
|
63619
|
+
var version$1 = "0.9.3";
|
|
63601
63620
|
//#endregion
|
|
63602
63621
|
//#region src/sentry.ts
|
|
63603
63622
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -66164,11 +66183,14 @@ function resolveConnectDeviceIdentity(identityPath, options) {
|
|
|
66164
66183
|
function resolveConnectRelayConfig(stateDir) {
|
|
66165
66184
|
if (!readConnectState(Path$1.join(stateDir, "connect.json")).linked) return {
|
|
66166
66185
|
linked: false,
|
|
66167
|
-
bridgeToken: void 0
|
|
66186
|
+
bridgeToken: void 0,
|
|
66187
|
+
runtimeId: void 0
|
|
66168
66188
|
};
|
|
66189
|
+
const secrets = resolveConnectDeviceSecrets(Path$1.join(stateDir, "connect-device.json"));
|
|
66169
66190
|
return {
|
|
66170
66191
|
linked: true,
|
|
66171
|
-
bridgeToken:
|
|
66192
|
+
bridgeToken: secrets.bridgeToken,
|
|
66193
|
+
runtimeId: secrets.runtimeId
|
|
66172
66194
|
};
|
|
66173
66195
|
}
|
|
66174
66196
|
//#endregion
|
|
@@ -66326,6 +66348,7 @@ var ServerConfig = class ServerConfig extends Service$1()("forge/config/ServerCo
|
|
|
66326
66348
|
desktopBootstrapToken: void 0,
|
|
66327
66349
|
desktopRelayBridgeToken: void 0,
|
|
66328
66350
|
connectRelayBridgeToken: void 0,
|
|
66351
|
+
connectRelayRuntimeId: void 0,
|
|
66329
66352
|
connectRelayLinked: false,
|
|
66330
66353
|
connectDisplayName: "forge",
|
|
66331
66354
|
connectPlatform: "linux",
|
|
@@ -68487,6 +68510,20 @@ function registerProxyRoute(route) {
|
|
|
68487
68510
|
return add$1(route.method, route.forgePath, handler);
|
|
68488
68511
|
}
|
|
68489
68512
|
//#endregion
|
|
68513
|
+
//#region src/connect/credentialRefresh.ts
|
|
68514
|
+
async function refreshConnectCredential(deps) {
|
|
68515
|
+
if (deps.refreshToken.length === 0) return null;
|
|
68516
|
+
try {
|
|
68517
|
+
return toConnectCredential(await deps.postJson(`${deps.commandAppUrl}/api/mcp-server/oauth/token`, {
|
|
68518
|
+
grant_type: "refresh_token",
|
|
68519
|
+
refresh_token: deps.refreshToken,
|
|
68520
|
+
client_id: deps.clientId
|
|
68521
|
+
}));
|
|
68522
|
+
} catch {
|
|
68523
|
+
return null;
|
|
68524
|
+
}
|
|
68525
|
+
}
|
|
68526
|
+
//#endregion
|
|
68490
68527
|
//#region src/relay/localBridge.ts
|
|
68491
68528
|
const DESKTOP_RELAY_CONTEXT_HEADER = "x-forge-relay-context";
|
|
68492
68529
|
const decodeRelayContext = decodeUnknownSync(RelayLocalBridgeContext);
|
|
@@ -68759,6 +68796,30 @@ function makeRelayDeliveryMiddlewareLayer(context, inbox, reconcileInterruptedCo
|
|
|
68759
68796
|
});
|
|
68760
68797
|
}
|
|
68761
68798
|
//#endregion
|
|
68799
|
+
//#region src/relay/bridgeAccess.ts
|
|
68800
|
+
function relayBridgeAccessAllowed(input) {
|
|
68801
|
+
if (!input.isLoopback) return false;
|
|
68802
|
+
if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
|
|
68803
|
+
return input.mode === "desktop" || input.connectLinked;
|
|
68804
|
+
}
|
|
68805
|
+
function resolveAuthorizedBridgeRuntimeId(input) {
|
|
68806
|
+
const isDesktop = input.mode === "desktop";
|
|
68807
|
+
if (!relayBridgeAccessAllowed({
|
|
68808
|
+
mode: input.mode,
|
|
68809
|
+
connectLinked: input.connectLinked,
|
|
68810
|
+
isLoopback: input.isLoopback,
|
|
68811
|
+
suppliedToken: input.suppliedToken,
|
|
68812
|
+
expectedToken: isDesktop ? input.desktopToken : input.connectToken
|
|
68813
|
+
})) return null;
|
|
68814
|
+
return (isDesktop ? input.desktopRuntimeId : input.connectRuntimeId) ?? null;
|
|
68815
|
+
}
|
|
68816
|
+
//#endregion
|
|
68817
|
+
//#region src/relay/controlPlaneAuthority.ts
|
|
68818
|
+
function selectControlPlaneAuthority(input) {
|
|
68819
|
+
if (input.mode === "desktop") return "session";
|
|
68820
|
+
return input.connectLinked ? "credential" : null;
|
|
68821
|
+
}
|
|
68822
|
+
//#endregion
|
|
68762
68823
|
//#region src/desktop/relayOwnerSession.ts
|
|
68763
68824
|
var DesktopRelayOwnerSessionError = class extends TaggedError("DesktopRelayOwnerSessionError") {};
|
|
68764
68825
|
const resolveDesktopRelayOwnerSession = (request) => gen(function* () {
|
|
@@ -68854,6 +68915,8 @@ function relayControlPlaneBodyMatchesRuntime(path, body, configuredRuntimeId) {
|
|
|
68854
68915
|
if (!isRecord$17(body) || !isRecord$17(body.runtime)) return false;
|
|
68855
68916
|
return body.runtime.runtimeId === configuredRuntimeId;
|
|
68856
68917
|
}
|
|
68918
|
+
/** Matches the client id `connect login` used, so CMMD accepts the refresh. */
|
|
68919
|
+
const CONNECT_CONTROL_PLANE_CLIENT_ID = "cmmd-forge-web";
|
|
68857
68920
|
function errorResponse(status, code, error) {
|
|
68858
68921
|
return jsonUnsafe({
|
|
68859
68922
|
error,
|
|
@@ -68872,9 +68935,16 @@ function relayProxyErrorResponse(error) {
|
|
|
68872
68935
|
return buildProxyErrorResponse(error);
|
|
68873
68936
|
}
|
|
68874
68937
|
function authorizedBridgeRuntimeId(request, config) {
|
|
68875
|
-
|
|
68876
|
-
|
|
68877
|
-
|
|
68938
|
+
return resolveAuthorizedBridgeRuntimeId({
|
|
68939
|
+
mode: config.mode === "desktop" ? "desktop" : "web",
|
|
68940
|
+
connectLinked: config.connectRelayLinked,
|
|
68941
|
+
isLoopback: isLoopbackRelayRequest(request),
|
|
68942
|
+
suppliedToken: request.headers[DESKTOP_RELAY_BRIDGE_TOKEN_HEADER],
|
|
68943
|
+
desktopToken: config.desktopRelayBridgeToken,
|
|
68944
|
+
connectToken: config.connectRelayBridgeToken,
|
|
68945
|
+
desktopRuntimeId: config.desktopRelayRuntimeId,
|
|
68946
|
+
connectRuntimeId: config.connectRelayRuntimeId
|
|
68947
|
+
});
|
|
68878
68948
|
}
|
|
68879
68949
|
function validateBridgeOperation(request, runtimeId) {
|
|
68880
68950
|
if (request.runtimeId !== runtimeId) return {
|
|
@@ -68901,9 +68971,10 @@ function validateBridgeOperation(request, runtimeId) {
|
|
|
68901
68971
|
}
|
|
68902
68972
|
function dispatchRelayControlPlaneOperation(input) {
|
|
68903
68973
|
return gen(function* () {
|
|
68904
|
-
const
|
|
68905
|
-
|
|
68906
|
-
|
|
68974
|
+
const caller = input.caller;
|
|
68975
|
+
const resolveJwt = (forceRefresh = false) => caller.kind === "credential" ? forceRefresh ? promise(() => caller.renew()) : succeed(caller.accessToken) : resolveCmmdJwtForSession({
|
|
68976
|
+
sessionId: caller.sessionId,
|
|
68977
|
+
subject: caller.subject,
|
|
68907
68978
|
logPrefix: "desktop-relay-control-plane",
|
|
68908
68979
|
forceRefresh
|
|
68909
68980
|
});
|
|
@@ -68925,19 +68996,57 @@ function dispatchRelayControlPlaneOperation(input) {
|
|
|
68925
68996
|
}
|
|
68926
68997
|
const desktopRelayControlPlaneRouteLayer = add$1("POST", DESKTOP_RELAY_CONTROL_PLANE_PATH, gen(function* () {
|
|
68927
68998
|
const request = yield* HttpServerRequest;
|
|
68928
|
-
const
|
|
68999
|
+
const config = yield* ServerConfig;
|
|
69000
|
+
const runtimeId = authorizedBridgeRuntimeId(request, config);
|
|
68929
69001
|
if (!runtimeId) return forbiddenResponse();
|
|
68930
69002
|
const decoded = yield* schemaBodyJson(DesktopRelayControlPlaneRequest).pipe(result);
|
|
68931
69003
|
if (isFailure$1(decoded)) return errorResponse(400, "RELAY_BRIDGE_REQUEST_INVALID", "Invalid relay bridge request.");
|
|
68932
69004
|
const operation = validateBridgeOperation(decoded.success, runtimeId);
|
|
68933
69005
|
if (!operation.ok) return operation.response;
|
|
69006
|
+
const authority = selectControlPlaneAuthority({
|
|
69007
|
+
mode: config.mode === "desktop" ? "desktop" : "web",
|
|
69008
|
+
connectLinked: config.connectRelayLinked
|
|
69009
|
+
});
|
|
69010
|
+
if (authority === null) return forbiddenResponse();
|
|
69011
|
+
if (authority === "credential") {
|
|
69012
|
+
const credential = readConnectCredential(Path$1.join(config.stateDir, "connect-credential.json"), createFileSecretStorage({ keyPath: Path$1.join(config.stateDir, "connect-secret.key") }));
|
|
69013
|
+
if (!credential) return errorResponse(401, "CMMD_AUTH_REQUIRED", "This machine is not signed in to CMMD. Run `cmmd-forge connect login`.");
|
|
69014
|
+
const credentialPath = Path$1.join(config.stateDir, "connect-credential.json");
|
|
69015
|
+
const secrets = createFileSecretStorage({ keyPath: Path$1.join(config.stateDir, "connect-secret.key") });
|
|
69016
|
+
return yield* dispatchRelayControlPlaneOperation({
|
|
69017
|
+
caller: {
|
|
69018
|
+
kind: "credential",
|
|
69019
|
+
accessToken: credential.accessToken,
|
|
69020
|
+
renew: async () => {
|
|
69021
|
+
const renewed = await refreshConnectCredential({
|
|
69022
|
+
commandAppUrl: resolveCommandAppUrl(),
|
|
69023
|
+
clientId: CONNECT_CONTROL_PLANE_CLIENT_ID,
|
|
69024
|
+
refreshToken: credential.refreshToken,
|
|
69025
|
+
postJson: async (url, body) => await (await fetch(url, {
|
|
69026
|
+
method: "POST",
|
|
69027
|
+
headers: { "content-type": "application/json" },
|
|
69028
|
+
body: JSON.stringify(body)
|
|
69029
|
+
})).json()
|
|
69030
|
+
});
|
|
69031
|
+
if (!renewed) return null;
|
|
69032
|
+
writeConnectCredential(credentialPath, secrets, renewed);
|
|
69033
|
+
return renewed.accessToken;
|
|
69034
|
+
}
|
|
69035
|
+
},
|
|
69036
|
+
path: operation.path,
|
|
69037
|
+
body: operation.body
|
|
69038
|
+
});
|
|
69039
|
+
}
|
|
68934
69040
|
const ownerResult = yield* resolveDesktopRelayOwnerSession(request).pipe(result);
|
|
68935
69041
|
if (isFailure$1(ownerResult)) {
|
|
68936
69042
|
const error = ownerResult.failure;
|
|
68937
69043
|
return errorResponse(error.status, error.code, error.message);
|
|
68938
69044
|
}
|
|
68939
69045
|
return yield* dispatchRelayControlPlaneOperation({
|
|
68940
|
-
|
|
69046
|
+
caller: {
|
|
69047
|
+
kind: "session",
|
|
69048
|
+
...ownerResult.success
|
|
69049
|
+
},
|
|
68941
69050
|
path: operation.path,
|
|
68942
69051
|
body: operation.body
|
|
68943
69052
|
});
|
|
@@ -97589,13 +97698,6 @@ async function reconcileCmmdWorkRunPrMerged(deps, input) {
|
|
|
97589
97698
|
};
|
|
97590
97699
|
}
|
|
97591
97700
|
//#endregion
|
|
97592
|
-
//#region src/relay/bridgeAccess.ts
|
|
97593
|
-
function relayBridgeAccessAllowed(input) {
|
|
97594
|
-
if (!input.isLoopback) return false;
|
|
97595
|
-
if (!bridgeTokenMatches(input.suppliedToken, input.expectedToken)) return false;
|
|
97596
|
-
return input.mode === "desktop" || input.connectLinked;
|
|
97597
|
-
}
|
|
97598
|
-
//#endregion
|
|
97599
97701
|
//#region ../../packages/shared/src/sessionScopes.ts
|
|
97600
97702
|
/**
|
|
97601
97703
|
* Which RPC scopes a direct-WebSocket session holds, by its role.
|
|
@@ -307155,6 +307257,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
|
|
|
307155
307257
|
desktopBootstrapToken,
|
|
307156
307258
|
desktopRelayBridgeToken,
|
|
307157
307259
|
connectRelayBridgeToken: connectRelay.bridgeToken,
|
|
307260
|
+
connectRelayRuntimeId: connectRelay.runtimeId,
|
|
307158
307261
|
connectRelayLinked: connectRelay.linked,
|
|
307159
307262
|
connectDisplayName: NodeOS.hostname(),
|
|
307160
307263
|
connectPlatform,
|
|
@@ -307573,7 +307676,8 @@ const connectLoginCommand = make$32("login", {
|
|
|
307573
307676
|
noBrowser: noBrowserLoginFlag,
|
|
307574
307677
|
email: loginEmailFlag
|
|
307575
307678
|
}).pipe(withDescription$1("Sign this machine in to CMMD so it can be offered over the relay."), withHandler((flags) => gen(function* () {
|
|
307576
|
-
const
|
|
307679
|
+
const config = yield* resolveCliAuthConfig(flags, yield* LogLevel);
|
|
307680
|
+
const stateDir = config.stateDir;
|
|
307577
307681
|
const startedAt = Date.now();
|
|
307578
307682
|
const emailOption = flags.email;
|
|
307579
307683
|
if (isSome(emailOption)) {
|
|
@@ -307588,7 +307692,7 @@ const connectLoginCommand = make$32("login", {
|
|
|
307588
307692
|
exchange: (email, secret) => runPromise(exchangeCmmdPasswordForOAuthToken({
|
|
307589
307693
|
email,
|
|
307590
307694
|
password: secret,
|
|
307591
|
-
redirectUri:
|
|
307695
|
+
redirectUri: resolveLoginRedirectUri({ port: config.port })
|
|
307592
307696
|
})).catch((cause) => {
|
|
307593
307697
|
if (isRejectedCredential(cause)) return {};
|
|
307594
307698
|
throw cause;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmmd-center/forge",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@cmmd-forge/client-runtime": "0.0.0-alpha.1",
|
|
47
47
|
"@cmmd-forge/contracts": "0.9.0",
|
|
48
|
+
"@cmmd-forge/relay-host": "0.0.0-alpha.1",
|
|
48
49
|
"@cmmd-forge/shared": "0.0.0-alpha.1",
|
|
49
50
|
"@cmmd-forge/web": "0.9.0",
|
|
50
51
|
"@effect/language-service": "0.84.2",
|
|
@@ -56,8 +57,7 @@
|
|
|
56
57
|
"effect-acp": "0.0.1",
|
|
57
58
|
"tsdown": "0.21.4",
|
|
58
59
|
"typescript": "^5.7.3",
|
|
59
|
-
"vitest": "^4.1.10"
|
|
60
|
-
"@cmmd-forge/relay-host": "0.0.0-alpha.1"
|
|
60
|
+
"vitest": "^4.1.10"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|
|
63
63
|
"node": "^22.16 || ^23.11 || >=24.10"
|