diff --git a/apps/server/src/auth/ServerSecretStore.test.ts b/apps/server/src/auth/ServerSecretStore.test.ts index 93339f4d4db..f18e59e6293 100644 --- a/apps/server/src/auth/ServerSecretStore.test.ts +++ b/apps/server/src/auth/ServerSecretStore.test.ts @@ -1,10 +1,11 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { expect, it } from "@effect/vitest"; +import { assert, it } from "@effect/vitest"; import * as Cause from "effect/Cause"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; import * as PlatformError from "effect/PlatformError"; @@ -145,13 +146,13 @@ const makeConcurrentCreateSecretStoreLayer = () => ); it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { - it.effect("returns null when a secret file does not exist", () => + it.effect("returns Option.none when a secret file does not exist", () => Effect.gen(function* () { const secretStore = yield* ServerSecretStore.ServerSecretStore; const secret = yield* secretStore.get("missing-secret"); - expect(secret).toBeNull(); + assert.isTrue(Option.isNone(secret)); }).pipe(Effect.provide(makeServerSecretStoreLayer())), ); @@ -162,7 +163,7 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { const first = yield* secretStore.getOrCreateRandom("session-signing-key", 32); const second = yield* secretStore.getOrCreateRandom("session-signing-key", 32); - expect(Array.from(second)).toEqual(Array.from(first)); + assert.deepEqual(Array.from(second), Array.from(first)); }).pipe(Effect.provide(makeServerSecretStoreLayer())), ); @@ -178,10 +179,10 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { { concurrency: "unbounded" }, ); const persisted = yield* secretStore.get("session-signing-key"); + const persistedBytes = Option.getOrThrow(persisted); - expect(persisted).not.toBeNull(); - expect(Array.from(first)).toEqual(Array.from(persisted ?? new Uint8Array())); - expect(Array.from(second)).toEqual(Array.from(persisted ?? new Uint8Array())); + assert.deepEqual(Array.from(first), Array.from(persistedBytes)); + assert.deepEqual(Array.from(second), Array.from(persistedBytes)); }).pipe(Effect.provide(makeConcurrentCreateSecretStoreLayer())), ); @@ -217,10 +218,10 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3])); - expect(chmodCalls.some((call) => call.mode === 0o700 && call.path.endsWith("/secrets"))).toBe( - true, + assert.isTrue( + chmodCalls.some((call) => call.mode === 0o700 && call.path.endsWith("/secrets")), ); - expect(chmodCalls.filter((call) => call.mode === 0o600).length).toBeGreaterThanOrEqual(2); + assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2); }).pipe(Effect.provide(NodeServices.layer)), ); @@ -230,10 +231,10 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { const error = yield* Effect.flip(secretStore.getOrCreateRandom("session-signing-key", 32)); - expect(error).toBeInstanceOf(ServerSecretStore.SecretStoreError); - expect(error.message).toContain("Failed to read secret session-signing-key."); - expect(error.cause).toBeInstanceOf(PlatformError.PlatformError); - expect((error.cause as PlatformError.PlatformError).reason._tag).toBe("PermissionDenied"); + assert.instanceOf(error, ServerSecretStore.SecretStoreError); + assert.include(error.message, "Failed to read secret session-signing-key."); + assert.instanceOf(error.cause, PlatformError.PlatformError); + assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); }).pipe(Effect.provide(makePermissionDeniedSecretStoreLayer())), ); @@ -245,10 +246,10 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3])), ); - expect(error).toBeInstanceOf(ServerSecretStore.SecretStoreError); - expect(error.message).toContain("Failed to persist secret session-signing-key."); - expect(error.cause).toBeInstanceOf(PlatformError.PlatformError); - expect((error.cause as PlatformError.PlatformError).reason._tag).toBe("PermissionDenied"); + assert.instanceOf(error, ServerSecretStore.SecretStoreError); + assert.include(error.message, "Failed to persist secret session-signing-key."); + assert.instanceOf(error.cause, PlatformError.PlatformError); + assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); }).pipe(Effect.provide(makeRenameFailureSecretStoreLayer())), ); @@ -258,10 +259,10 @@ it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => { const error = yield* Effect.flip(secretStore.remove("session-signing-key")); - expect(error).toBeInstanceOf(ServerSecretStore.SecretStoreError); - expect(error.message).toContain("Failed to remove secret session-signing-key."); - expect(error.cause).toBeInstanceOf(PlatformError.PlatformError); - expect((error.cause as PlatformError.PlatformError).reason._tag).toBe("PermissionDenied"); + assert.instanceOf(error, ServerSecretStore.SecretStoreError); + assert.include(error.message, "Failed to remove secret session-signing-key."); + assert.instanceOf(error.cause, PlatformError.PlatformError); + assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied"); }).pipe(Effect.provide(makeRemoveFailureSecretStoreLayer())), ); }); diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index 3b84ba58377..0dc4a6bb544 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -1,19 +1,23 @@ import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; -import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Predicate from "effect/Predicate"; import * as PlatformError from "effect/PlatformError"; +import * as Schema from "effect/Schema"; import { ServerConfig } from "../config.ts"; -export class SecretStoreError extends Data.TaggedError("SecretStoreError")<{ - readonly message: string; - readonly cause?: unknown; -}> {} +export class SecretStoreError extends Schema.TaggedErrorClass()( + "SecretStoreError", + { + message: Schema.String, + cause: Schema.optional(Schema.Defect()), + }, +) {} const isPlatformError = (value: unknown): value is PlatformError.PlatformError => Predicate.isTagged(value, "PlatformError"); @@ -22,7 +26,7 @@ export const isSecretAlreadyExistsError = (error: SecretStoreError): boolean => isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists"; export interface ServerSecretStoreShape { - readonly get: (name: string) => Effect.Effect; + readonly get: (name: string) => Effect.Effect, SecretStoreError>; readonly set: (name: string, value: Uint8Array) => Effect.Effect; readonly create: (name: string, value: Uint8Array) => Effect.Effect; readonly getOrCreateRandom: ( @@ -57,10 +61,10 @@ export const make = Effect.fn("makeServerSecretStore")(function* () { const get: ServerSecretStoreShape["get"] = (name) => fileSystem.readFile(resolveSecretPath(name)).pipe( - Effect.map((bytes) => Uint8Array.from(bytes)), + Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" - ? Effect.succeed(null) + ? Effect.succeed(Option.none()) : Effect.fail( new SecretStoreError({ message: `Failed to read secret ${name}.`, @@ -133,41 +137,43 @@ export const make = Effect.fn("makeServerSecretStore")(function* () { const getOrCreateRandom: ServerSecretStoreShape["getOrCreateRandom"] = (name, bytes) => get(name).pipe( - Effect.flatMap((existing) => { - if (existing) { - return Effect.succeed(existing); - } - - return crypto.randomBytes(bytes).pipe( - Effect.mapError( - (cause) => - new SecretStoreError({ - message: `Failed to generate random bytes for secret ${name}.`, - cause, - }), - ), - Effect.flatMap((generated) => - create(name, generated).pipe( - Effect.as(Uint8Array.from(generated)), - Effect.catchTag("SecretStoreError", (error) => - isSecretAlreadyExistsError(error) - ? get(name).pipe( - Effect.flatMap((created) => - created !== null - ? Effect.succeed(created) - : Effect.fail( - new SecretStoreError({ - message: `Failed to read secret ${name} after concurrent creation.`, - }), - ), - ), - ) - : Effect.fail(error), + Effect.flatMap( + Option.match({ + onSome: Effect.succeed, + onNone: () => + crypto.randomBytes(bytes).pipe( + Effect.mapError( + (cause) => + new SecretStoreError({ + message: `Failed to generate random bytes for secret ${name}.`, + cause, + }), + ), + Effect.flatMap((generated) => + create(name, generated).pipe( + Effect.as(Uint8Array.from(generated)), + Effect.catchTag("SecretStoreError", (error) => + isSecretAlreadyExistsError(error) + ? get(name).pipe( + Effect.flatMap( + Option.match({ + onSome: Effect.succeed, + onNone: () => + Effect.fail( + new SecretStoreError({ + message: `Failed to read secret ${name} after concurrent creation.`, + }), + ), + }), + ), + ) + : Effect.fail(error), + ), + ), ), ), - ), - ); - }), + }), + ), Effect.withSpan("ServerSecretStore.getOrCreateRandom"), ); diff --git a/apps/server/src/cli/connect.ts b/apps/server/src/cli/connect.ts index 54f9fd40da9..9c8fb17a18b 100644 --- a/apps/server/src/cli/connect.ts +++ b/apps/server/src/cli/connect.ts @@ -385,9 +385,9 @@ const connectStatusCommand = Command.make("status", { const status: CloudCliStatus = { desired, authenticated, - linked: cloudUserId !== null, - cloudUserId: cloudUserId ? bytesToString(cloudUserId) : null, - relayUrl: relayUrl ? bytesToString(relayUrl) : null, + linked: Option.isSome(cloudUserId), + cloudUserId: Option.isSome(cloudUserId) ? bytesToString(cloudUserId.value) : null, + relayUrl: Option.isSome(relayUrl) ? bytesToString(relayUrl.value) : null, relayClient: executable, }; yield* Console.log(formatCloudStatus(status, { json: flags.json })); diff --git a/apps/server/src/cloud/CliState.test.ts b/apps/server/src/cloud/CliState.test.ts index 2798f5b6ede..3fbf4f12db2 100644 --- a/apps/server/src/cloud/CliState.test.ts +++ b/apps/server/src/cloud/CliState.test.ts @@ -1,7 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { expect, it } from "@effect/vitest"; +import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { ServerConfig } from "../config.ts"; @@ -40,18 +41,18 @@ it.layer(NodeServices.layer)("CliState", (it) => { Effect.gen(function* () { const secrets = yield* ServerSecretStore.ServerSecretStore; - expect(yield* CliState.readCliDesiredCloudLink).toBe(false); + assert.isFalse(yield* CliState.readCliDesiredCloudLink); yield* CliState.setCliDesiredCloudLink(true); - expect(yield* CliState.readCliDesiredCloudLink).toBe(true); + assert.isTrue(yield* CliState.readCliDesiredCloudLink); for (const name of persistedCloudLinkSecrets) { yield* secrets.set(name, new TextEncoder().encode(name)); } yield* CliState.clearPersistedCloudLink; - expect(yield* CliState.readCliDesiredCloudLink).toBe(false); + assert.isFalse(yield* CliState.readCliDesiredCloudLink); for (const name of persistedCloudLinkSecrets) { - expect(yield* secrets.get(name)).toBe(null); + assert.isTrue(Option.isNone(yield* secrets.get(name))); } }).pipe(Effect.provide(makeTestLayer())), ); diff --git a/apps/server/src/cloud/CliState.ts b/apps/server/src/cloud/CliState.ts index f344a0b73cc..2e18fff4250 100644 --- a/apps/server/src/cloud/CliState.ts +++ b/apps/server/src/cloud/CliState.ts @@ -1,4 +1,5 @@ import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { @@ -17,7 +18,7 @@ const TRUE_BYTES = new TextEncoder().encode("true"); export const readCliDesiredCloudLink = Effect.gen(function* () { const secrets = yield* ServerSecretStore.ServerSecretStore; - return (yield* secrets.get(CLOUD_CLI_DESIRED_LINK_SECRET)) !== null; + return Option.isSome(yield* secrets.get(CLOUD_CLI_DESIRED_LINK_SECRET)); }); export const setCliDesiredCloudLink = Effect.fn("cloud.cli_state.set_desired")(function* ( diff --git a/apps/server/src/cloud/CliTokenManager.ts b/apps/server/src/cloud/CliTokenManager.ts index 765ef058332..88a61f5df74 100644 --- a/apps/server/src/cloud/CliTokenManager.ts +++ b/apps/server/src/cloud/CliTokenManager.ts @@ -100,8 +100,8 @@ const make = Effect.gen(function* () { const read = Effect.fn("cloud.cli_token.read")(function* () { const encoded = yield* secrets.get(CLOUD_CLI_OAUTH_TOKEN_SECRET); - if (!encoded) return Option.none(); - return Option.some(yield* decodePersistedToken(bytesToString(encoded))); + if (Option.isNone(encoded)) return Option.none(); + return Option.some(yield* decodePersistedToken(bytesToString(encoded.value))); }); const exchangeToken = Effect.fn("cloud.cli_token.exchange")(function* ( diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.ts b/apps/server/src/cloud/ManagedEndpointRuntime.ts index 7c8735b12e0..f2eedaf0c6d 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.ts @@ -22,10 +22,10 @@ function bytesToString(bytes: Uint8Array): string { const readRuntimeConfig = Effect.gen(function* () { const secrets = yield* ServerSecretStore.ServerSecretStore; const bytes = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG); - if (!bytes) { + if (Option.isNone(bytes)) { return null; } - return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes))); + return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes.value))); }); export interface CloudManagedEndpointRuntimeShape { diff --git a/apps/server/src/cloud/environmentKeys.test.ts b/apps/server/src/cloud/environmentKeys.test.ts index 3a033d50303..5c20cd64ed3 100644 --- a/apps/server/src/cloud/environmentKeys.test.ts +++ b/apps/server/src/cloud/environmentKeys.test.ts @@ -1,7 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { expect, it } from "@effect/vitest"; +import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; @@ -23,10 +24,10 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it const first = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore); const second = yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore); - expect(second).toEqual(first); - expect(yield* secretStore.get("cloud-link-ed25519-key-pair")).not.toBeNull(); - expect(yield* secretStore.get("cloud-link-ed25519-private-key")).toBeNull(); - expect(yield* secretStore.get("cloud-link-ed25519-public-key")).toBeNull(); + assert.deepEqual(second, first); + assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair"))); + assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-private-key"))); + assert.isTrue(Option.isNone(yield* secretStore.get("cloud-link-ed25519-public-key"))); }).pipe(Effect.provide(makeServerSecretStoreLayer())), ); @@ -36,11 +37,11 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it yield* secretStore.set("cloud-link-ed25519-private-key", new TextEncoder().encode("private")); yield* secretStore.set("cloud-link-ed25519-public-key", new TextEncoder().encode("public")); - expect(yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore)).toEqual({ + assert.deepEqual(yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore), { privateKey: "private", publicKey: "public", }); - expect(yield* secretStore.get("cloud-link-ed25519-key-pair")).not.toBeNull(); + assert.isTrue(Option.isSome(yield* secretStore.get("cloud-link-ed25519-key-pair"))); }).pipe(Effect.provide(makeServerSecretStoreLayer())), ); @@ -53,7 +54,9 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it const secretStore = { get: (name) => Effect.sync(() => - name === "cloud-link-ed25519-key-pair" && createAttempted ? winner : null, + name === "cloud-link-ed25519-key-pair" && createAttempted + ? Option.some(winner) + : Option.none(), ), set: unusedSecretStoreOperation, create: () => @@ -78,7 +81,7 @@ it.layer(NodeServices.layer)("getOrCreateEnvironmentKeyPairFromSecretStore", (it remove: unusedSecretStoreOperation, } satisfies ServerSecretStore.ServerSecretStoreShape; - expect(yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore)).toEqual({ + assert.deepEqual(yield* getOrCreateEnvironmentKeyPairFromSecretStore(secretStore), { privateKey: "winner-private", publicKey: "winner-public", }); diff --git a/apps/server/src/cloud/environmentKeys.ts b/apps/server/src/cloud/environmentKeys.ts index beef4729992..f051d8265cb 100644 --- a/apps/server/src/cloud/environmentKeys.ts +++ b/apps/server/src/cloud/environmentKeys.ts @@ -1,5 +1,6 @@ import * as NodeCrypto from "node:crypto"; import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; @@ -33,14 +34,15 @@ const readEnvironmentKeyPair = Effect.fn("readEnvironmentKeyPair")(function* ( secrets: ServerSecretStore.ServerSecretStoreShape, ) { const encoded = yield* secrets.get(CLOUD_LINK_KEY_PAIR); - if (encoded === null) { - return null; + if (Option.isNone(encoded)) { + return Option.none(); } - return yield* decodeEnvironmentKeyPair(bytesToString(encoded)).pipe( + const decoded = yield* decodeEnvironmentKeyPair(bytesToString(encoded.value)).pipe( Effect.mapError((cause) => keyPairPersistenceError("Failed to decode environment signing key pair.", cause), ), ); + return Option.some(decoded); }); const persistEnvironmentKeyPair = Effect.fn("persistEnvironmentKeyPair")(function* ( @@ -57,14 +59,16 @@ const persistEnvironmentKeyPair = Effect.fn("persistEnvironmentKeyPair")(functio Effect.catchTag("SecretStoreError", (error) => ServerSecretStore.isSecretAlreadyExistsError(error) ? readEnvironmentKeyPair(secrets).pipe( - Effect.flatMap((existing) => - existing !== null - ? Effect.succeed(existing) - : Effect.fail( + Effect.flatMap( + Option.match({ + onSome: Effect.succeed, + onNone: () => + Effect.fail( keyPairPersistenceError( "Failed to read environment signing key pair after concurrent creation.", ), ), + }), ), ) : Effect.fail(error), @@ -76,16 +80,16 @@ export const getOrCreateEnvironmentKeyPairFromSecretStore = Effect.fn(function* secrets: ServerSecretStore.ServerSecretStoreShape, ) { const existing = yield* readEnvironmentKeyPair(secrets); - if (existing !== null) { - return existing; + if (Option.isSome(existing)) { + return existing.value; } const existingPrivate = yield* secrets.get(CLOUD_LINK_PRIVATE_KEY); const existingPublic = yield* secrets.get(CLOUD_LINK_PUBLIC_KEY); - if (existingPrivate && existingPublic) { + if (Option.isSome(existingPrivate) && Option.isSome(existingPublic)) { return yield* persistEnvironmentKeyPair(secrets, { - privateKey: bytesToString(existingPrivate), - publicKey: bytesToString(existingPublic), + privateKey: bytesToString(existingPrivate.value), + publicKey: bytesToString(existingPublic.value), }); } diff --git a/apps/server/src/cloud/http.ts b/apps/server/src/cloud/http.ts index 89928ae13a2..773891124c5 100644 --- a/apps/server/src/cloud/http.ts +++ b/apps/server/src/cloud/http.ts @@ -220,10 +220,10 @@ function validateLinkedCloudUser(input: { }), ), Effect.flatMap((existing) => { - if (!existing) { + if (Option.isNone(existing)) { return Effect.void; } - const existingCloudUserId = bytesToString(existing); + const existingCloudUserId = bytesToString(existing.value); return existingCloudUserId === input.cloudUserId ? Effect.void : Effect.fail( @@ -248,8 +248,8 @@ function readInstalledCloudUserId( }), ), Effect.flatMap((bytes) => - bytes - ? Effect.succeed(bytesToString(bytes)) + Option.isSome(bytes) + ? Effect.succeed(bytesToString(bytes.value)) : Effect.fail( new EnvironmentAuth.ServerAuthInternalError({ message: "Cloud linked user is not installed for this environment.", @@ -622,12 +622,12 @@ const readCloudLinkState = Effect.fn("environment.cloud.readLinkState")(function { concurrency: 4 }, ); return { - linked: cloudUserId !== null, - cloudUserId: cloudUserId ? bytesToString(cloudUserId) : null, - relayUrl: relayUrl ? bytesToString(relayUrl) : null, - relayIssuer: relayIssuer ? bytesToString(relayIssuer) : null, - publishAgentActivity: publishAgentActivity - ? bytesToString(publishAgentActivity) === "true" + linked: Option.isSome(cloudUserId), + cloudUserId: Option.isSome(cloudUserId) ? bytesToString(cloudUserId.value) : null, + relayUrl: Option.isSome(relayUrl) ? bytesToString(relayUrl.value) : null, + relayIssuer: Option.isSome(relayIssuer) ? bytesToString(relayIssuer.value) : null, + publishAgentActivity: Option.isSome(publishAgentActivity) + ? bytesToString(publishAgentActivity.value) === "true" : false, } satisfies EnvironmentCloudLinkStateResult; }); @@ -690,8 +690,8 @@ const cloudEnvironmentHealthHandler = Effect.fn("environment.cloud.health")( function* (dependencies: CloudHttpDependencies, request: RelayCloudEnvironmentHealthRequest) { const cloudMintPublicKey = yield* dependencies.secrets.get(CLOUD_MINT_PUBLIC_KEY).pipe( Effect.flatMap((bytes) => - bytes - ? Effect.succeed(bytesToString(bytes)) + Option.isSome(bytes) + ? Effect.succeed(bytesToString(bytes.value)) : Effect.fail( new EnvironmentAuth.ServerAuthInternalError({ message: "Cloud mint public key is not installed for this environment.", @@ -701,12 +701,12 @@ const cloudEnvironmentHealthHandler = Effect.fn("environment.cloud.health")( ); const relayIssuer = yield* dependencies.secrets.get(RELAY_ISSUER_SECRET).pipe( Effect.flatMap((bytes) => - bytes - ? Effect.succeed(bytesToString(bytes)) + Option.isSome(bytes) + ? Effect.succeed(bytesToString(bytes.value)) : dependencies.secrets.get(RELAY_URL_SECRET).pipe( Effect.flatMap((fallbackBytes) => - fallbackBytes - ? Effect.succeed(bytesToString(fallbackBytes)) + Option.isSome(fallbackBytes) + ? Effect.succeed(bytesToString(fallbackBytes.value)) : Effect.fail( new EnvironmentAuth.ServerAuthInternalError({ message: "Cloud relay issuer is not installed for this environment.", @@ -807,8 +807,8 @@ const cloudMintCredentialHandler = Effect.fn("environment.cloud.mintCredential") function* (dependencies: CloudHttpDependencies, request: RelayCloudMintCredentialRequest) { const cloudMintPublicKey = yield* dependencies.secrets.get(CLOUD_MINT_PUBLIC_KEY).pipe( Effect.flatMap((bytes) => - bytes - ? Effect.succeed(bytesToString(bytes)) + Option.isSome(bytes) + ? Effect.succeed(bytesToString(bytes.value)) : Effect.fail( new EnvironmentAuth.ServerAuthInternalError({ message: "Cloud mint public key is not installed for this environment.", @@ -818,12 +818,12 @@ const cloudMintCredentialHandler = Effect.fn("environment.cloud.mintCredential") ); const relayIssuer = yield* dependencies.secrets.get(RELAY_ISSUER_SECRET).pipe( Effect.flatMap((bytes) => - bytes - ? Effect.succeed(bytesToString(bytes)) + Option.isSome(bytes) + ? Effect.succeed(bytesToString(bytes.value)) : dependencies.secrets.get(RELAY_URL_SECRET).pipe( Effect.flatMap((fallbackBytes) => - fallbackBytes - ? Effect.succeed(bytesToString(fallbackBytes)) + Option.isSome(fallbackBytes) + ? Effect.succeed(bytesToString(fallbackBytes.value)) : Effect.fail( new EnvironmentAuth.ServerAuthInternalError({ message: "Cloud relay issuer is not installed for this environment.", diff --git a/apps/server/src/relay/AgentAwarenessRelay.test.ts b/apps/server/src/relay/AgentAwarenessRelay.test.ts index 6bdf62b104f..a5c3dca86f9 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.test.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.test.ts @@ -64,9 +64,10 @@ function makeMemorySecretStore() { const values = new Map(); const store = { get: ((name) => - Effect.sync( - () => values.get(name) ?? null, - )) satisfies ServerSecretStore.ServerSecretStoreShape["get"], + Effect.sync(() => { + const value = values.get(name); + return value === undefined ? Option.none() : Option.some(Uint8Array.from(value)); + })) satisfies ServerSecretStore.ServerSecretStoreShape["get"], set: ((name, value) => Effect.sync(() => { values.set(name, Uint8Array.from(value)); diff --git a/apps/server/src/relay/AgentAwarenessRelay.ts b/apps/server/src/relay/AgentAwarenessRelay.ts index 280f61bcb20..91babdce4eb 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.ts @@ -276,7 +276,13 @@ const make = Effect.gen(function* () { const publishedStateByThreadRef = yield* Ref.make(new Map()); const readSecretString = (name: string) => - secrets.get(name).pipe(Effect.map((bytes) => (bytes ? new TextDecoder().decode(bytes) : null))); + secrets + .get(name) + .pipe( + Effect.map((bytes) => + Option.isSome(bytes) ? new TextDecoder().decode(bytes.value) : null, + ), + ); const readRelayConfig = Effect.gen(function* () { const [url, issuer, environmentCredential] = yield* Effect.all([ diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index 0e126604b4a..6e1ceb16a8d 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -32,6 +32,7 @@ import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Equal from "effect/Equal"; import * as PubSub from "effect/PubSub"; @@ -350,7 +351,7 @@ const makeServerSettings = Effect.gen(function* () { ); environment.push({ ...variable, - value: secret ? textDecoder.decode(secret) : "", + value: Option.isSome(secret) ? textDecoder.decode(secret.value) : "", }); } providerInstances[instanceId] = {