History

[codex] Structure desktop persisted credential errors (#3239)

Julius Marminge committed 4 months ago

Commit c1d8a221 parent 7b791894 files · +291 −93Browse files

Unified · Split

4 files
  1. apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts +15 −1
  2. apps/desktop/src/app/DesktopConnectionCatalogStore.ts +138 −57
  3. apps/desktop/src/settings/DesktopSavedEnvironments.test.ts +18 −3
  4. apps/desktop/src/settings/DesktopSavedEnvironments.ts +120 −32
apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts+15−1
@@ -393,7 +393,21 @@ describe("DesktopConnectionCatalogStore", () => { · 392 unmodified lines
393393 assert.isTrue(yield* store.set('{"schemaVersion":1,"targets":[]}'));
394394 yield* Ref.set(failDecrypt, true);
395395 const error = yield* store.get.pipe(Effect.flip);
396− assert.instanceOf(error, ElectronSafeStorage.ElectronSafeStorageDecryptError);
396+ assert.instanceOf(
397+ error,
398+ DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreProtectionError,
399+ );
400+ assert.equal(error.operation, "decrypt-catalog");
401+ assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
402+ assert.instanceOf(error.cause, ElectronSafeStorage.ElectronSafeStorageDecryptError);
403+ const decryptError = error.cause as ElectronSafeStorage.ElectronSafeStorageDecryptError;
404+ assert.instanceOf(decryptError.cause, Error);
405+ assert.equal(decryptError.cause.message, "invalid encrypted catalog");
406+ assert.equal(
407+ error.message,
408+ `Desktop connection catalog protection failed during decrypt-catalog at ${baseDir}/userdata/connection-catalog.json.`,
409+ );
410+ assert.notEqual(error.message, decryptError.message);
397411 yield* Ref.set(failDecrypt, false);
398412 assert.deepStrictEqual(yield* store.get, Option.some('{"schemaVersion":1,"targets":[]}'));
399413 }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
apps/desktop/src/app/DesktopConnectionCatalogStore.ts+138−57
@@ -53,8 +53,6 @@ const DesktopConnectionCatalogStoreWriteOperation = Schema.Literals([ · 52 unmodified lines
5353 "write-temporary-file",
5454 "replace-catalog-file",
5555 ]);
56−type DesktopConnectionCatalogStoreWriteOperation =
57− typeof DesktopConnectionCatalogStoreWriteOperation.Type;
5856
5957 const DesktopConnectionCatalogStoreMigrationOperation = Schema.Literals([
6058 "read-legacy-registry",
@@ -62,8 +60,12 @@ const DesktopConnectionCatalogStoreMigrationOperation = Schema.Literals([ · 1 unmodified line
6260 "encode-catalog",
6361 "persist-catalog",
6462 ]);
65−type DesktopConnectionCatalogStoreMigrationOperation =
66− typeof DesktopConnectionCatalogStoreMigrationOperation.Type;
63+
64+const DesktopConnectionCatalogStoreProtectionOperation = Schema.Literals([
65+ "check-encryption-availability",
66+ "encrypt-catalog",
67+ "decrypt-catalog",
68+]);
6769
6870 export class DesktopConnectionCatalogStoreWriteError extends Schema.TaggedErrorClass<DesktopConnectionCatalogStoreWriteError>()(
6971 "DesktopConnectionCatalogStoreWriteError",
@@ -78,17 +80,6 @@ export class DesktopConnectionCatalogStoreWriteError extends Schema.TaggedErrorC · 8 unmodified lines
7880 }
7981 }
8082
81−const writeError = (
82− operation: DesktopConnectionCatalogStoreWriteOperation,
83− path: string,
84− cause: unknown,
85−): DesktopConnectionCatalogStoreWriteError =>
86− new DesktopConnectionCatalogStoreWriteError({
87− operation,
88− path,
89− cause,
90− });
91−
9283 export class DesktopConnectionCatalogStoreDecodeError extends Schema.TaggedErrorClass<DesktopConnectionCatalogStoreDecodeError>()(
9384 "DesktopConnectionCatalogStoreDecodeError",
9485 {
@@ -142,18 +133,18 @@ export class DesktopConnectionCatalogStoreMigrationError extends Schema.TaggedEr · 47 unmodified lines
142133 }
143134 }
144135
145−const migrationError = (
146− operation: DesktopConnectionCatalogStoreMigrationOperation,
147− catalogPath: string,
148− cause: unknown,
149− environmentId?: string,
150−): DesktopConnectionCatalogStoreMigrationError =>
151− new DesktopConnectionCatalogStoreMigrationError({
152− operation,
153− catalogPath,
154− ...(environmentId === undefined ? {} : { environmentId }),
155− cause,
156− });
136+export class DesktopConnectionCatalogStoreProtectionError extends Schema.TaggedErrorClass<DesktopConnectionCatalogStoreProtectionError>()(
137+ "DesktopConnectionCatalogStoreProtectionError",
138+ {
139+ operation: DesktopConnectionCatalogStoreProtectionOperation,
140+ catalogPath: Schema.String,
141+ cause: Schema.Defect(),
142+ },
143+) {
144+ override get message(): string {
145+ return `Desktop connection catalog protection failed during ${this.operation} at ${this.catalogPath}.`;
146+ }
147+}
157148
158149 export class DesktopConnectionCatalogStore extends Context.Service<
159150 DesktopConnectionCatalogStore,
@@ -164,13 +155,13 @@ export class DesktopConnectionCatalogStore extends Context.Service< · 4 unmodified lines
164155 | DesktopConnectionCatalogStoreDocumentDecodeError
165156 | DesktopConnectionCatalogStoreDecodeError
166157 | DesktopConnectionCatalogStoreMigrationError
167− | ElectronSafeStorage.ElectronSafeStorageError
158+ | DesktopConnectionCatalogStoreProtectionError
168159 >;
169160 readonly set: (
170161 catalog: string,
171162 ) => Effect.Effect<
172163 boolean,
173− DesktopConnectionCatalogStoreWriteError | ElectronSafeStorage.ElectronSafeStorageError
164+ DesktopConnectionCatalogStoreWriteError | DesktopConnectionCatalogStoreProtectionError
174165 >;
175166 readonly clear: Effect.Effect<void>;
176167 }
@@ -236,20 +227,46 @@ const writeDocument = Effect.fn("desktop.connectionCatalogStore.writeDocument")( · 59 unmodified lines
236227 const directory = input.path.dirname(input.catalogPath);
237228 const tempPath = `${input.catalogPath}.${process.pid}.${input.suffix}.tmp`;
238229 const encoded = yield* encodeEncryptedConnectionCatalogDocumentJson(input.document).pipe(
239− Effect.mapError((cause) => writeError("encode-document", input.catalogPath, cause)),
230+ Effect.mapError(
231+ (cause) =>
232+ new DesktopConnectionCatalogStoreWriteError({
233+ operation: "encode-document",
234+ path: input.catalogPath,
235+ cause,
236+ }),
237+ ),
238+ );
239+ yield* input.fileSystem.makeDirectory(directory, { recursive: true }).pipe(
240+ Effect.mapError(
241+ (cause) =>
242+ new DesktopConnectionCatalogStoreWriteError({
243+ operation: "create-directory",
244+ path: directory,
245+ cause,
246+ }),
247+ ),
240248 );
241− yield* input.fileSystem
242− .makeDirectory(directory, { recursive: true })
243− .pipe(Effect.mapError((cause) => writeError("create-directory", directory, cause)));
244249 yield* Effect.gen(function* () {
245− yield* input.fileSystem
246− .writeFileString(tempPath, `${encoded}\n`)
247− .pipe(Effect.mapError((cause) => writeError("write-temporary-file", tempPath, cause)));
248− yield* input.fileSystem
249− .rename(tempPath, input.catalogPath)
250− .pipe(
251− Effect.mapError((cause) => writeError("replace-catalog-file", input.catalogPath, cause)),
252− );
250+ yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`).pipe(
251+ Effect.mapError(
252+ (cause) =>
253+ new DesktopConnectionCatalogStoreWriteError({
254+ operation: "write-temporary-file",
255+ path: tempPath,
256+ cause,
257+ }),
258+ ),
259+ );
260+ yield* input.fileSystem.rename(tempPath, input.catalogPath).pipe(
261+ Effect.mapError(
262+ (cause) =>
263+ new DesktopConnectionCatalogStoreWriteError({
264+ operation: "replace-catalog-file",
265+ path: input.catalogPath,
266+ cause,
267+ }),
268+ ),
269+ );
253270 }).pipe(
254271 Effect.ensuring(
255272 input.fileSystem.remove(tempPath, { force: true }).pipe(
@@ -330,13 +347,17 @@ const migrateSavedEnvironmentRecords = Effect.fn( · 74 unmodified lines
330347 wsBaseUrl: record.wsBaseUrl,
331348 }),
332349 );
333− const token = yield* savedEnvironments
334− .getSecret(record.environmentId)
335− .pipe(
336− Effect.mapError((cause) =>
337− migrationError("read-legacy-secret", catalogPath, cause, record.environmentId),
338− ),
339− );
350+ const token = yield* savedEnvironments.getSecret(record.environmentId).pipe(
351+ Effect.mapError(
352+ (cause) =>
353+ new DesktopConnectionCatalogStoreMigrationError({
354+ operation: "read-legacy-secret",
355+ catalogPath,
356+ environmentId: record.environmentId,
357+ cause,
358+ }),
359+ ),
360+ );
340361 if (Option.isSome(token)) {
341362 credentials.push({
342363 connectionId: id,
@@ -362,13 +383,41 @@ export const make = Effect.gen(function* () { · 19 unmodified lines
362383 const crypto = yield* Crypto.Crypto;
363384 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
364385 const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
386+ const encryptionAvailable = safeStorage.isEncryptionAvailable.pipe(
387+ Effect.mapError(
388+ (cause) =>
389+ new DesktopConnectionCatalogStoreProtectionError({
390+ operation: "check-encryption-availability",
391+ catalogPath,
392+ cause,
393+ }),
394+ ),
395+ );
365396
366397 const writeCatalog = Effect.fn("desktop.connectionCatalogStore.writeCatalog")(function* (
367398 catalog: string,
368399 ) {
369− const encryptedCatalog = Encoding.encodeBase64(yield* safeStorage.encryptString(catalog));
400+ const encryptedCatalog = Encoding.encodeBase64(
401+ yield* safeStorage.encryptString(catalog).pipe(
402+ Effect.mapError(
403+ (cause) =>
404+ new DesktopConnectionCatalogStoreProtectionError({
405+ operation: "encrypt-catalog",
406+ catalogPath,
407+ cause,
408+ }),
409+ ),
410+ ),
411+ );
370412 const suffix = (yield* crypto.randomUUIDv4.pipe(
371− Effect.mapError((cause) => writeError("create-temporary-file-name", catalogPath, cause)),
413+ Effect.mapError(
414+ (cause) =>
415+ new DesktopConnectionCatalogStoreWriteError({
416+ operation: "create-temporary-file-name",
417+ path: catalogPath,
418+ cause,
419+ }),
420+ ),
372421 )).replace(/-/g, "");
373422 yield* writeDocument({
374423 fileSystem,
@@ -380,21 +429,42 @@ export const make = Effect.gen(function* () { · 5 unmodified lines
380429 });
381430
382431 const migrateLegacyCatalog = Effect.gen(function* () {
383− if (!(yield* safeStorage.isEncryptionAvailable)) {
432+ if (!(yield* encryptionAvailable)) {
384433 return Option.none<string>();
385434 }
386435 const records = yield* savedEnvironments.getRegistry.pipe(
387− Effect.mapError((cause) => migrationError("read-legacy-registry", catalogPath, cause)),
436+ Effect.mapError(
437+ (cause) =>
438+ new DesktopConnectionCatalogStoreMigrationError({
439+ operation: "read-legacy-registry",
440+ catalogPath,
441+ cause,
442+ }),
443+ ),
388444 );
389445 if (records.length === 0) {
390446 return Option.none<string>();
391447 }
392448 const catalog = yield* migrateSavedEnvironmentRecords(records, savedEnvironments, catalogPath);
393449 const encoded = yield* encodeRuntimeConnectionCatalogDocumentJson(catalog).pipe(
394− Effect.mapError((cause) => migrationError("encode-catalog", catalogPath, cause)),
450+ Effect.mapError(
451+ (cause) =>
452+ new DesktopConnectionCatalogStoreMigrationError({
453+ operation: "encode-catalog",
454+ catalogPath,
455+ cause,
456+ }),
457+ ),
395458 );
396459 yield* writeCatalog(encoded).pipe(
397− Effect.mapError((cause) => migrationError("persist-catalog", catalogPath, cause)),
460+ Effect.mapError(
461+ (cause) =>
462+ new DesktopConnectionCatalogStoreMigrationError({
463+ operation: "persist-catalog",
464+ catalogPath,
465+ cause,
466+ }),
467+ ),
398468 );
399469 return Option.some(encoded);
400470 });
@@ -405,16 +475,27 @@ export const make = Effect.gen(function* () { · 4 unmodified lines
405475 if (Option.isNone(document)) {
406476 return yield* migrateLegacyCatalog;
407477 }
408− if (!(yield* safeStorage.isEncryptionAvailable)) {
478+ if (!(yield* encryptionAvailable)) {
409479 return Option.none<string>();
410480 }
411481 const decrypted = yield* decodeSecretBytes(catalogPath, document.value.encryptedCatalog).pipe(
412− Effect.flatMap(safeStorage.decryptString),
482+ Effect.flatMap((encryptedCatalog) =>
483+ safeStorage.decryptString(encryptedCatalog).pipe(
484+ Effect.mapError(
485+ (cause) =>
486+ new DesktopConnectionCatalogStoreProtectionError({
487+ operation: "decrypt-catalog",
488+ catalogPath,
489+ cause,
490+ }),
491+ ),
492+ ),
493+ ),
413494 );
414495 return Option.some(decrypted);
415496 }).pipe(Effect.withSpan("desktop.connectionCatalogStore.get")),
416497 set: Effect.fn("desktop.connectionCatalogStore.set")(function* (catalog) {
417− if (!(yield* safeStorage.isEncryptionAvailable)) {
498+ if (!(yield* encryptionAvailable)) {
418499 return false;
419500 }
420501 yield* writeCatalog(catalog);
apps/desktop/src/settings/DesktopSavedEnvironments.test.ts+18−3
@@ -271,10 +271,11 @@ describe("DesktopSavedEnvironments", () => { · 270 unmodified lines
271271 ),
272272 );
273273
274− it.effect("surfaces typed safe storage availability failures", () => {
274+ it.effect("adds saved-environment context to safe storage availability failures", () => {
275275 const cause = new Error("safe storage unavailable");
276276 return withSavedEnvironments(
277277 Effect.gen(function* () {
278+ const environment = yield* DesktopEnvironment.DesktopEnvironment;
278279 const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
279280 yield* savedEnvironments.setRegistry([savedRegistryRecord]);
280281
@@ -285,8 +286,22 @@ describe("DesktopSavedEnvironments", () => { · 4 unmodified lines
285286 })
286287 .pipe(Effect.flip);
287288
288− assert.instanceOf(error, ElectronSafeStorage.ElectronSafeStorageAvailabilityError);
289− assert.equal(error.cause, cause);
289+ assert.instanceOf(
290+ error,
291+ DesktopSavedEnvironments.DesktopSavedEnvironmentSecretProtectionError,
292+ );
293+ assert.equal(error.operation, "check-encryption-availability");
294+ assert.equal(error.environmentId, savedRegistryRecord.environmentId);
295+ assert.equal(error.registryPath, environment.savedEnvironmentRegistryPath);
296+ assert.instanceOf(error.cause, ElectronSafeStorage.ElectronSafeStorageAvailabilityError);
297+ const availabilityError =
298+ error.cause as ElectronSafeStorage.ElectronSafeStorageAvailabilityError;
299+ assert.strictEqual(availabilityError.cause, cause);
300+ assert.equal(
301+ error.message,
302+ `Desktop saved-environment secret protection failed during check-encryption-availability for environment ${savedRegistryRecord.environmentId} at ${environment.savedEnvironmentRegistryPath}.`,
303+ );
304+ assert.notEqual(error.message, availabilityError.message);
290305 }),
291306 { availabilityError: cause },
292307 );
apps/desktop/src/settings/DesktopSavedEnvironments.ts+120−32
@@ -77,7 +77,12 @@ const DesktopSavedEnvironmentsWriteOperation = Schema.Literals([ · 76 unmodified lines
7777 "write-temporary-file",
7878 "replace-registry-file",
7979 ]);
80−type DesktopSavedEnvironmentsWriteOperation = typeof DesktopSavedEnvironmentsWriteOperation.Type;
80+
81+const DesktopSavedEnvironmentSecretProtectionOperation = Schema.Literals([
82+ "check-encryption-availability",
83+ "encrypt-secret",
84+ "decrypt-secret",
85+]);
8186
8287 export class DesktopSavedEnvironmentsWriteError extends Schema.TaggedErrorClass<DesktopSavedEnvironmentsWriteError>()(
8388 "DesktopSavedEnvironmentsWriteError",
@@ -92,17 +97,6 @@ export class DesktopSavedEnvironmentsWriteError extends Schema.TaggedErrorClass< · 8 unmodified lines
9297 }
9398 }
9499
95−const writeError = (
96− operation: DesktopSavedEnvironmentsWriteOperation,
97− path: string,
98− cause: unknown,
99−): DesktopSavedEnvironmentsWriteError =>
100− new DesktopSavedEnvironmentsWriteError({
101− operation,
102− path,
103− cause,
104− });
105−
106100 export class DesktopSavedEnvironmentsReadError extends Schema.TaggedErrorClass<DesktopSavedEnvironmentsReadError>()(
107101 "DesktopSavedEnvironmentsReadError",
108102 {
@@ -141,6 +135,20 @@ export class DesktopSavedEnvironmentSecretDecodeError extends Schema.TaggedError · 32 unmodified lines
141135 }
142136 }
143137
138+export class DesktopSavedEnvironmentSecretProtectionError extends Schema.TaggedErrorClass<DesktopSavedEnvironmentSecretProtectionError>()(
139+ "DesktopSavedEnvironmentSecretProtectionError",
140+ {
141+ operation: DesktopSavedEnvironmentSecretProtectionOperation,
142+ environmentId: Schema.String,
143+ registryPath: Schema.String,
144+ cause: Schema.Defect(),
145+ },
146+) {
147+ override get message(): string {
148+ return `Desktop saved-environment secret protection failed during ${this.operation} for environment ${this.environmentId} at ${this.registryPath}.`;
149+ }
150+}
151+
144152 export type DesktopSavedEnvironmentsReadRegistryError =
145153 | DesktopSavedEnvironmentsReadError
146154 | DesktopSavedEnvironmentsDocumentDecodeError;
@@ -152,11 +160,11 @@ export type DesktopSavedEnvironmentsMutationError = · 5 unmodified lines
152160 export type DesktopSavedEnvironmentsGetSecretError =
153161 | DesktopSavedEnvironmentsReadRegistryError
154162 | DesktopSavedEnvironmentSecretDecodeError
155− | ElectronSafeStorage.ElectronSafeStorageError;
163+ | DesktopSavedEnvironmentSecretProtectionError;
156164
157165 export type DesktopSavedEnvironmentsSetSecretError =
158166 | DesktopSavedEnvironmentsMutationError
159− | ElectronSafeStorage.ElectronSafeStorageError;
167+ | DesktopSavedEnvironmentSecretProtectionError;
160168
161169 export class DesktopSavedEnvironments extends Context.Service<
162170 DesktopSavedEnvironments,
@@ -276,19 +284,45 @@ const writeRegistryDocument = Effect.fn("desktop.savedEnvironments.writeRegistry · 113 unmodified lines
276284 const directory = input.path.dirname(input.registryPath);
277285 const tempPath = `${input.registryPath}.${process.pid}.${input.suffix}.tmp`;
278286 const encoded = yield* encodeSavedEnvironmentRegistryDocumentJson(input.document).pipe(
279− Effect.mapError((cause) => writeError("encode-registry", input.registryPath, cause)),
287+ Effect.mapError(
288+ (cause) =>
289+ new DesktopSavedEnvironmentsWriteError({
290+ operation: "encode-registry",
291+ path: input.registryPath,
292+ cause,
293+ }),
294+ ),
295+ );
296+ yield* input.fileSystem.makeDirectory(directory, { recursive: true }).pipe(
297+ Effect.mapError(
298+ (cause) =>
299+ new DesktopSavedEnvironmentsWriteError({
300+ operation: "create-directory",
301+ path: directory,
302+ cause,
303+ }),
304+ ),
305+ );
306+ yield* input.fileSystem.writeFileString(tempPath, `${encoded}\n`).pipe(
307+ Effect.mapError(
308+ (cause) =>
309+ new DesktopSavedEnvironmentsWriteError({
310+ operation: "write-temporary-file",
311+ path: tempPath,
312+ cause,
313+ }),
314+ ),
315+ );
316+ yield* input.fileSystem.rename(tempPath, input.registryPath).pipe(
317+ Effect.mapError(
318+ (cause) =>
319+ new DesktopSavedEnvironmentsWriteError({
320+ operation: "replace-registry-file",
321+ path: input.registryPath,
322+ cause,
323+ }),
324+ ),
280325 );
281− yield* input.fileSystem
282− .makeDirectory(directory, { recursive: true })
283− .pipe(Effect.mapError((cause) => writeError("create-directory", directory, cause)));
284− yield* input.fileSystem
285− .writeFileString(tempPath, `${encoded}\n`)
286− .pipe(Effect.mapError((cause) => writeError("write-temporary-file", tempPath, cause)));
287− yield* input.fileSystem
288− .rename(tempPath, input.registryPath)
289− .pipe(
290− Effect.mapError((cause) => writeError("replace-registry-file", input.registryPath, cause)),
291− );
292326 },
293327 );
294328
@@ -341,8 +375,13 @@ export const make = Effect.gen(function* () { · 46 unmodified lines
341375 const writeDocument = (document: SavedEnvironmentRegistryDocument) =>
342376 crypto.randomUUIDv4.pipe(
343377 Effect.map((uuid) => uuid.replace(/-/g, "")),
344− Effect.mapError((cause) =>
345− writeError("create-temporary-file-name", environment.savedEnvironmentRegistryPath, cause),
378+ Effect.mapError(
379+ (cause) =>
380+ new DesktopSavedEnvironmentsWriteError({
381+ operation: "create-temporary-file-name",
382+ path: environment.savedEnvironmentRegistryPath,
383+ cause,
384+ }),
346385 ),
347386 Effect.flatMap((suffix) =>
348387 writeRegistryDocument({
@@ -396,7 +435,21 @@ export const make = Effect.gen(function* () { · 47 unmodified lines
396435 document.records.find((record) => record.environmentId === environmentId)
397436 ?.encryptedBearerToken,
398437 );
399− if (Option.isNone(encoded) || !(yield* safeStorage.isEncryptionAvailable)) {
438+ if (Option.isNone(encoded)) {
439+ return Option.none<string>();
440+ }
441+ const encryptionAvailable = yield* safeStorage.isEncryptionAvailable.pipe(
442+ Effect.mapError(
443+ (cause) =>
444+ new DesktopSavedEnvironmentSecretProtectionError({
445+ operation: "check-encryption-availability",
446+ environmentId,
447+ registryPath: environment.savedEnvironmentRegistryPath,
448+ cause,
449+ }),
450+ ),
451+ );
452+ if (!encryptionAvailable) {
400453 return Option.none<string>();
401454 }
402455
@@ -405,7 +458,19 @@ export const make = Effect.gen(function* () { · 2 unmodified lines
405458 environment.savedEnvironmentRegistryPath,
406459 encoded.value,
407460 );
408− return Option.some(yield* safeStorage.decryptString(secretBytes));
461+ return Option.some(
462+ yield* safeStorage.decryptString(secretBytes).pipe(
463+ Effect.mapError(
464+ (cause) =>
465+ new DesktopSavedEnvironmentSecretProtectionError({
466+ operation: "decrypt-secret",
467+ environmentId,
468+ registryPath: environment.savedEnvironmentRegistryPath,
469+ cause,
470+ }),
471+ ),
472+ ),
473+ );
409474 }),
410475 setSecret: Effect.fn("desktop.savedEnvironments.setSecret")(function* (input) {
411476 const { environmentId, secret } = input;
@@ -415,11 +480,34 @@ export const make = Effect.gen(function* () { · 3 unmodified lines
415480 environment.savedEnvironmentRegistryPath,
416481 );
417482
418− if (!(yield* safeStorage.isEncryptionAvailable)) {
483+ const encryptionAvailable = yield* safeStorage.isEncryptionAvailable.pipe(
484+ Effect.mapError(
485+ (cause) =>
486+ new DesktopSavedEnvironmentSecretProtectionError({
487+ operation: "check-encryption-availability",
488+ environmentId,
489+ registryPath: environment.savedEnvironmentRegistryPath,
490+ cause,
491+ }),
492+ ),
493+ );
494+ if (!encryptionAvailable) {
419495 return false;
420496 }
421497
422− const encryptedBearerToken = Encoding.encodeBase64(yield* safeStorage.encryptString(secret));
498+ const encryptedBearerToken = Encoding.encodeBase64(
499+ yield* safeStorage.encryptString(secret).pipe(
500+ Effect.mapError(
501+ (cause) =>
502+ new DesktopSavedEnvironmentSecretProtectionError({
503+ operation: "encrypt-secret",
504+ environmentId,
505+ registryPath: environment.savedEnvironmentRegistryPath,
506+ cause,
507+ }),
508+ ),
509+ ),
510+ );
423511 let found = false;
424512 const nextDocument: SavedEnvironmentRegistryDocument = {
425513 version: document.version,