History

fix(server): decouple CLI identity from launcher IPC

Bil0000 committed 2 months ago

Commit 56048cd1 parent 2aebbc112 files · +87 −24Browse files

Unified · Split

12 files
  1. apps/server/src/auth/EnvironmentAuth.test.ts +1 −1
  2. apps/server/src/auth/EnvironmentAuth.ts +1 −1
  3. apps/server/src/auth/EnvironmentAuthAdmin.test.ts +1 −1
  4. apps/server/src/auth/EnvironmentAuthPolicy.test.ts +1 −2
  5. apps/server/src/auth/EnvironmentAuthPolicy.ts +1 −1
  6. apps/server/src/auth/SessionStore.test.ts +1 −2
  7. apps/server/src/auth/SessionStore.ts +1 −1
  8. apps/server/src/bin.test.ts +26 −3
  9. apps/server/src/cli/connect.ts +2 −3
  10. apps/server/src/cli/pair.test.ts +24 −1
  11. apps/server/src/environment/ServerEnvironment.ts +27 −6
  12. apps/server/src/server.test.ts +1 −2
apps/server/src/auth/EnvironmentAuth.test.ts+1−1
@@ -35,7 +35,7 @@ const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfig.ServerConfig[ · 34 unmodified lines
35 EnvironmentAuth.layer.pipe(35 EnvironmentAuth.layer.pipe(
36 Layer.provide(SqlitePersistenceMemory),36 Layer.provide(SqlitePersistenceMemory),
37 Layer.provide(ServerSecretStore.layer),37 Layer.provide(ServerSecretStore.layer),
38 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),38 Layer.provide(ServerEnvironment.identityLayer),
39 Layer.provide(makeServerConfigLayer(overrides)),39 Layer.provide(makeServerConfigLayer(overrides)),
40 );40 );
41 41
apps/server/src/auth/EnvironmentAuth.ts+1−1
@@ -1023,5 +1023,5 @@ export const storageLayer = Layer.mergeAll(ServerSecretStore.layer, SqlitePersis · 1022 unmodified lines
1023 1023
1024export const runtimeLayer = layer.pipe(1024export const runtimeLayer = layer.pipe(
1025 Layer.provideMerge(storageLayer),1025 Layer.provideMerge(storageLayer),
1026 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),1026 Layer.provideMerge(ServerEnvironment.identityLayer),
1027);1027);
apps/server/src/auth/EnvironmentAuthAdmin.test.ts+1−1
@@ -36,7 +36,7 @@ const makeEnvironmentAuthLayer = ( · 35 unmodified lines
36 EnvironmentAuth.layer.pipe(36 EnvironmentAuth.layer.pipe(
37 Layer.provideMerge(ServerSecretStore.layer),37 Layer.provideMerge(ServerSecretStore.layer),
38 Layer.provideMerge(SqlitePersistenceMemory),38 Layer.provideMerge(SqlitePersistenceMemory),
39 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),39 Layer.provide(ServerEnvironment.identityLayer),
40 Layer.provide(makeServerConfigLayer(overrides)),40 Layer.provide(makeServerConfigLayer(overrides)),
41 );41 );
42 42
apps/server/src/auth/EnvironmentAuthPolicy.test.ts+1−2
@@ -6,13 +6,12 @@ import * as Layer from "effect/Layer"; · 5 unmodified lines
6import * as ServerConfig from "../config.ts";6import * as ServerConfig from "../config.ts";
7import * as ServerEnvironment from "../environment/ServerEnvironment.ts";7import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
8import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts";8import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts";
9import * as ServerSecretStore from "./ServerSecretStore.ts";
10 9
11const makeEnvironmentAuthPolicyLayer = (10const makeEnvironmentAuthPolicyLayer = (
12 overrides?: Partial<ServerConfig.ServerConfig["Service"]>,11 overrides?: Partial<ServerConfig.ServerConfig["Service"]>,
13) =>12) =>
14 EnvironmentAuthPolicy.layer.pipe(13 EnvironmentAuthPolicy.layer.pipe(
15 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),14 Layer.provide(ServerEnvironment.identityLayer),
16 Layer.provide(15 Layer.provide(
17 Layer.effect(16 Layer.effect(
18 ServerConfig.ServerConfig,17 ServerConfig.ServerConfig,
apps/server/src/auth/EnvironmentAuthPolicy.ts+1−1
@@ -16,7 +16,7 @@ export class EnvironmentAuthPolicy extends Context.Service< · 15 unmodified lines
16 16
17export const make = Effect.gen(function* () {17export const make = Effect.gen(function* () {
18 const config = yield* ServerConfig.ServerConfig;18 const config = yield* ServerConfig.ServerConfig;
19 const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;19 const serverEnvironment = yield* ServerEnvironment.ServerEnvironmentIdentity;
20 const isRemoteReachable = isRemoteReachableHost(config.host);20 const isRemoteReachable = isRemoteReachableHost(config.host);
21 21
22 const policy =22 const policy =
apps/server/src/auth/SessionStore.test.ts+1−2
@@ -28,9 +28,8 @@ const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Se · 27 unmodified lines
28 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" })));28 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" })));
29 29
30const makeServerEnvironmentLayer = (environmentId: EnvironmentId) =>30const makeServerEnvironmentLayer = (environmentId: EnvironmentId) =>
31 Layer.succeed(ServerEnvironment.ServerEnvironment, {31 Layer.succeed(ServerEnvironment.ServerEnvironmentIdentity, {
32 getEnvironmentId: Effect.succeed(environmentId),32 getEnvironmentId: Effect.succeed(environmentId),
33 getDescriptor: Effect.die(new Error("unused in SessionStore tests")),
34 });33 });
35 34
36const makeSessionStoreLayer = (35const makeSessionStoreLayer = (
apps/server/src/auth/SessionStore.ts+1−1
@@ -473,7 +473,7 @@ function toAuthClientSession(input: Omit<AuthClientSession, "current">): AuthCli · 472 unmodified lines
473export const make = Effect.gen(function* () {473export const make = Effect.gen(function* () {
474 const crypto = yield* Crypto.Crypto;474 const crypto = yield* Crypto.Crypto;
475 const serverConfig = yield* ServerConfig.ServerConfig;475 const serverConfig = yield* ServerConfig.ServerConfig;
476 const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;476 const serverEnvironment = yield* ServerEnvironment.ServerEnvironmentIdentity;
477 const secretStore = yield* ServerSecretStore.ServerSecretStore;477 const secretStore = yield* ServerSecretStore.ServerSecretStore;
478 const authSessions = yield* AuthSessions.AuthSessionRepository;478 const authSessions = yield* AuthSessions.AuthSessionRepository;
479 const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME, 32);479 const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME, 32);
apps/server/src/bin.test.ts+26−3
@@ -13,6 +13,7 @@ import { · 12 unmodified lines
13 ThreadId,13 ThreadId,
14} from "@t3tools/contracts";14} from "@t3tools/contracts";
15import * as NetService from "@t3tools/shared/Net";15import * as NetService from "@t3tools/shared/Net";
16import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
16import { assert, it } from "@effect/vitest";17import { assert, it } from "@effect/vitest";
17import * as Effect from "effect/Effect";18import * as Effect from "effect/Effect";
18import * as DateTime from "effect/DateTime";19import * as DateTime from "effect/DateTime";
@@ -26,6 +27,11 @@ import * as TestConsole from "effect/testing/TestConsole"; · 7 unmodified lines
26import { Command } from "effect/unstable/cli";27import { Command } from "effect/unstable/cli";
27 28
28import { cli, makeCli } from "./bin.ts";29import { cli, makeCli } from "./bin.ts";
30import * as ServiceLauncherClient from "./cloud/serviceLauncherClient.ts";
31import {
32 SERVICE_LAUNCHER_CONTEXT_ENV,
33 SERVICE_LAUNCHER_PROTOCOL,
34} from "./cloud/serviceProtocol.ts";
29import * as ServerConfig from "./config.ts";35import * as ServerConfig from "./config.ts";
30import * as ServerEnvironment from "./environment/ServerEnvironment.ts";36import * as ServerEnvironment from "./environment/ServerEnvironment.ts";
31import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";37import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";
@@ -43,7 +49,24 @@ import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; · 11 unmodified lines
43import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";49import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
44import { environmentAuthenticatedAuthLayer } from "./auth/http.ts";50import { environmentAuthenticatedAuthLayer } from "./auth/http.ts";
45 51
52import packageJson from "../package.json" with { type: "json" };
53
46const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);54const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
55const DisconnectedLauncherChildLayer = Layer.mergeAll(
56 Layer.succeed(HostProcessEnvironment, {
57 ...process.env,
58 [SERVICE_LAUNCHER_CONTEXT_ENV]: JSON.stringify({
59 protocol: SERVICE_LAUNCHER_PROTOCOL,
60 childVersion: packageJson.version,
61 }),
62 }),
63 Layer.succeed(ServiceLauncherClient.ServiceLauncherHostProcess, {
64 connected: false,
65 send: () => false,
66 on: () => undefined,
67 off: () => undefined,
68 }),
69);
47class ProjectCliHttpApi extends HttpApi.make("environment").add(EnvironmentOrchestrationHttpApi) {}70class ProjectCliHttpApi extends HttpApi.make("environment").add(EnvironmentOrchestrationHttpApi) {}
48 71
49const connectCli = makeCli({ cloudEnabled: true });72const connectCli = makeCli({ cloudEnabled: true });
@@ -128,7 +151,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef · 78 unmodified lines
128 Layer.provideMerge(151 Layer.provideMerge(
129 EnvironmentAuth.layer.pipe(152 EnvironmentAuth.layer.pipe(
130 Layer.provideMerge(SqlitePersistenceLayerLive),153 Layer.provideMerge(SqlitePersistenceLayerLive),
131 Layer.provide(ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer))),154 Layer.provide(ServerEnvironment.identityLayer),
132 Layer.provide(ServerSecretStore.layer),155 Layer.provide(ServerSecretStore.layer),
133 ),156 ),
134 ),157 ),
@@ -239,7 +262,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 104 unmodified lines
239 assert.equal(status.linked, false);262 assert.equal(status.linked, false);
240 assert.equal(status.cloudUserId, null);263 assert.equal(status.cloudUserId, null);
241 assert.equal(status.relayUrl, null);264 assert.equal(status.relayUrl, null);
242 }),265 }).pipe(Effect.provide(DisconnectedLauncherChildLayer)),
243 );266 );
244 267
245 it.effect("reports actionable human-readable headless connect state", () =>268 it.effect("reports actionable human-readable headless connect state", () =>
@@ -410,7 +433,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 164 unmodified lines
410 "relay:write",433 "relay:write",
411 ]);434 ]);
412 assert.equal("token" in (listed[0] ?? {}), false);435 assert.equal("token" in (listed[0] ?? {}), false);
413 }),436 }).pipe(Effect.provide(DisconnectedLauncherChildLayer)),
414 );437 );
415 438
416 it.effect("rejects invalid ttl values before running auth commands", () =>439 it.effect("rejects invalid ttl values before running auth commands", () =>
apps/server/src/cli/connect.ts+2−3
@@ -337,7 +337,7 @@ const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(f · 336 unmodified lines
337 return { status: "not-authenticated" } satisfies RelayUnlinkResult;337 return { status: "not-authenticated" } satisfies RelayUnlinkResult;
338 }338 }
339 339
340 const environment = yield* ServerEnvironment.ServerEnvironment;340 const environment = yield* ServerEnvironment.ServerEnvironmentIdentity;
341 const environmentId = yield* environment.getEnvironmentId;341 const environmentId = yield* environment.getEnvironmentId;
342 const relayUrl = yield* relayUrlConfig;342 const relayUrl = yield* relayUrlConfig;
343 const httpClient = yield* HttpClient.HttpClient;343 const httpClient = yield* HttpClient.HttpClient;
@@ -432,7 +432,7 @@ const runCloudCommand = Effect.fn("cloud.cli.run_cloud_command")(function* <A, E · 88 unmodified lines
432 | HttpClient.HttpClient432 | HttpClient.HttpClient
433 | Prompt.Environment433 | Prompt.Environment
434 | ServerConfig.ServerConfig434 | ServerConfig.ServerConfig
435 | ServerEnvironment.ServerEnvironment435 | ServerEnvironment.ServerEnvironmentIdentity
436 >,436 >,
437 options?: {437 options?: {
438 readonly quietLogs?: boolean;438 readonly quietLogs?: boolean;
@@ -449,7 +449,6 @@ const runCloudCommand = Effect.fn("cloud.cli.run_cloud_command")(function* <A, E · 10 unmodified lines
449 ),449 ),
450 RelayClient.layerCloudflared({ baseDir: config.baseDir }),450 RelayClient.layerCloudflared({ baseDir: config.baseDir }),
451 EnvironmentAuth.runtimeLayer,451 EnvironmentAuth.runtimeLayer,
452 ServerEnvironment.layer.pipe(Layer.provide(ServerSecretStore.layer)),
453 bootServiceLayer(config),452 bootServiceLayer(config),
454 headlessRelayClientTracingLayer,453 headlessRelayClientTracingLayer,
455 ).pipe(454 ).pipe(
apps/server/src/cli/pair.test.ts+24−1
@@ -6,6 +6,7 @@ import * as NodePath from "node:path"; · 5 unmodified lines
6 6
7import * as NodeServices from "@effect/platform-node/NodeServices";7import * as NodeServices from "@effect/platform-node/NodeServices";
8import * as NetService from "@t3tools/shared/Net";8import * as NetService from "@t3tools/shared/Net";
9import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";
9import { assert, describe, expect, it } from "@effect/vitest";10import { assert, describe, expect, it } from "@effect/vitest";
10import * as Effect from "effect/Effect";11import * as Effect from "effect/Effect";
11import * as Layer from "effect/Layer";12import * as Layer from "effect/Layer";
@@ -13,6 +14,11 @@ import * as TestConsole from "effect/testing/TestConsole"; · 1 unmodified line
13import { Command } from "effect/unstable/cli";14import { Command } from "effect/unstable/cli";
14 15
15import { cli } from "../bin.ts";16import { cli } from "../bin.ts";
17import {
18 SERVICE_LAUNCHER_CONTEXT_ENV,
19 SERVICE_LAUNCHER_PROTOCOL,
20} from "../cloud/serviceProtocol.ts";
21import * as ServiceLauncherClient from "../cloud/serviceLauncherClient.ts";
16import {22import {
17 makePersistedServerRuntimeState,23 makePersistedServerRuntimeState,
18 persistServerRuntimeState,24 persistServerRuntimeState,
@@ -24,6 +30,8 @@ import { · 5 unmodified lines
24 resolveTailscaleLocalTarget,30 resolveTailscaleLocalTarget,
25} from "./pair.ts";31} from "./pair.ts";
26 32
33import packageJson from "../../package.json" with { type: "json" };
34
27const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);35const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
28 36
29const baseState = {37const baseState = {
@@ -170,7 +178,22 @@ describe("t3 pair", () => { · 140 unmodified lines
170 assert.equal(credentials.length, 1);178 assert.equal(credentials.length, 1);
171 assert.equal(credentials[0]?.label, "t3 pair");179 assert.equal(credentials[0]?.label, "t3 pair");
172 }),180 }),
173 ).pipe(Effect.provide(NodeServices.layer)),181 ).pipe(
182 Effect.provide(NodeServices.layer),
183 Effect.provideService(HostProcessEnvironment, {
184 ...process.env,
185 [SERVICE_LAUNCHER_CONTEXT_ENV]: JSON.stringify({
186 protocol: SERVICE_LAUNCHER_PROTOCOL,
187 childVersion: packageJson.version,
188 }),
189 }),
190 Effect.provideService(ServiceLauncherClient.ServiceLauncherHostProcess, {
191 connected: false,
192 send: () => false,
193 on: () => undefined,
194 off: () => undefined,
195 }),
196 ),
174 );197 );
175 198
176 it.effect("pairs through the recorded dev web URL for dev servers", () =>199 it.effect("pairs through the recorded dev web URL for dev servers", () =>
apps/server/src/environment/ServerEnvironment.ts+27−6
@@ -42,6 +42,13 @@ export class ServerEnvironment extends Context.Service< · 41 unmodified lines
42 }42 }
43>()("t3/environment/ServerEnvironment") {}43>()("t3/environment/ServerEnvironment") {}
44 44
45export class ServerEnvironmentIdentity extends Context.Service<
46 ServerEnvironmentIdentity,
47 {
48 readonly getEnvironmentId: Effect.Effect<EnvironmentId>;
49 }
50>()("t3/environment/ServerEnvironment/ServerEnvironmentIdentity") {}
51
45function platformOs(platform: NodeJS.Platform): ExecutionEnvironmentDescriptor["platform"]["os"] {52function platformOs(platform: NodeJS.Platform): ExecutionEnvironmentDescriptor["platform"]["os"] {
46 switch (platform) {53 switch (platform) {
47 case "darwin":54 case "darwin":
@@ -68,14 +75,10 @@ function platformArch( · 20 unmodified lines
68 }75 }
69}76}
70 77
71export const make = Effect.gen(function* () {78const makeIdentity = Effect.gen(function* () {
72 const fileSystem = yield* FileSystem.FileSystem;79 const fileSystem = yield* FileSystem.FileSystem;
73 const path = yield* Path.Path;
74 const serverConfig = yield* ServerConfig.ServerConfig;80 const serverConfig = yield* ServerConfig.ServerConfig;
75 const secrets = yield* ServerSecretStore.ServerSecretStore;
76 const crypto = yield* Crypto.Crypto;81 const crypto = yield* Crypto.Crypto;
77 const hostPlatform = yield* HostProcessPlatform;
78 const hostArchitecture = yield* HostProcessArchitecture;
79 82
80 const readPersistedEnvironmentId = Effect.gen(function* () {83 const readPersistedEnvironmentId = Effect.gen(function* () {
81 const exists = yield* fileSystem.exists(serverConfig.environmentIdPath).pipe(84 const exists = yield* fileSystem.exists(serverConfig.environmentIdPath).pipe(
@@ -131,6 +134,19 @@ export const make = Effect.gen(function* () { · 49 unmodified lines
131 });134 });
132 135
133 const environmentId = EnvironmentId.make(environmentIdRaw);136 const environmentId = EnvironmentId.make(environmentIdRaw);
137 return ServerEnvironmentIdentity.of({
138 getEnvironmentId: Effect.succeed(environmentId),
139 });
140});
141
142export const make = Effect.gen(function* () {
143 const path = yield* Path.Path;
144 const serverConfig = yield* ServerConfig.ServerConfig;
145 const secrets = yield* ServerSecretStore.ServerSecretStore;
146 const identity = yield* ServerEnvironmentIdentity;
147 const hostPlatform = yield* HostProcessPlatform;
148 const hostArchitecture = yield* HostProcessArchitecture;
149 const environmentId = yield* identity.getEnvironmentId;
134 const cwdBaseName = path.basename(serverConfig.cwd).trim();150 const cwdBaseName = path.basename(serverConfig.cwd).trim();
135 const label = yield* resolveServerEnvironmentLabel({ cwdBaseName });151 const label = yield* resolveServerEnvironmentLabel({ cwdBaseName });
136 const launcher = yield* resolveServiceLauncherMode();152 const launcher = yield* resolveServiceLauncherMode();
@@ -185,4 +201,9 @@ export const make = Effect.gen(function* () { · 48 unmodified lines
185 * provide the external platform services, a ServerConfig, and the201 * provide the external platform services, a ServerConfig, and the
186 * ServerSecretStore backing the descriptor's publishing capability.202 * ServerSecretStore backing the descriptor's publishing capability.
187 */203 */
188export const layer = Layer.effect(ServerEnvironment, make).pipe(Layer.provide(ProcessRunner.layer));204export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentity);
205
206export const layer = Layer.effect(ServerEnvironment, make).pipe(
207 Layer.provideMerge(identityLayer),
208 Layer.provide(ProcessRunner.layer),
209);
apps/server/src/server.test.ts+1−2
@@ -292,9 +292,8 @@ const makeAuthTestLayer = () => · 291 unmodified lines
292 Layer.provide(SqlitePersistenceMemory),292 Layer.provide(SqlitePersistenceMemory),
293 Layer.provide(ServerSecretStore.layer),293 Layer.provide(ServerSecretStore.layer),
294 Layer.provide(294 Layer.provide(
295 Layer.mock(ServerEnvironment.ServerEnvironment)({295 Layer.mock(ServerEnvironment.ServerEnvironmentIdentity)({
296 getEnvironmentId: Effect.succeed(testEnvironmentDescriptor.environmentId),296 getEnvironmentId: Effect.succeed(testEnvironmentDescriptor.environmentId),
297 getDescriptor: Effect.succeed(testEnvironmentDescriptor),
298 }),297 }),
299 ),298 ),
300 );299 );