1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { assert, it } from "@effect/vitest";
3import * as Cause from "effect/Cause";
4import * as Deferred from "effect/Deferred";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Layer from "effect/Layer";
8import * as Option from "effect/Option";
9import * as Ref from "effect/Ref";
10import * as PlatformError from "effect/PlatformError";
11
12import * as ServerConfig from "../config.ts";
13import * as ServerSecretStore from "./ServerSecretStore.ts";
14
15const layerServerConfig = () =>
16 ServerConfig.layerTest(process.cwd(), { prefix: "t3-secret-store-test-" });
17
18const layerServerSecretStore = () => Layer.provide(ServerSecretStore.layer, layerServerConfig());
19
20const layerPermissionDeniedFileSystem = Layer.effect(
21 FileSystem.FileSystem,
22 Effect.gen(function* () {
23 const fileSystem = yield* FileSystem.FileSystem;
24
25 return {
26 ...fileSystem,
27 readFile: (path) =>
28 Effect.fail(
29 PlatformError.systemError({
30 _tag: "PermissionDenied",
31 module: "FileSystem",
32 method: "readFile",
33 pathOrDescriptor: path,
34 description: "Permission denied while reading secret file.",
35 }),
36 ),
37 } satisfies FileSystem.FileSystem;
38 }),
39).pipe(Layer.provide(NodeServices.layer));
40
41const layerPermissionDeniedSecretStore = () =>
42 ServerSecretStore.layer.pipe(
43 Layer.provide(layerServerConfig()),
44 Layer.provideMerge(layerPermissionDeniedFileSystem),
45 );
46
47const layerRenameFailureFileSystem = Layer.effect(
48 FileSystem.FileSystem,
49 Effect.gen(function* () {
50 const fileSystem = yield* FileSystem.FileSystem;
51
52 return {
53 ...fileSystem,
54 rename: (from, to) =>
55 Effect.fail(
56 PlatformError.systemError({
57 _tag: "PermissionDenied",
58 module: "FileSystem",
59 method: "rename",
60 pathOrDescriptor: `${String(from)} -> ${String(to)}`,
61 description: "Permission denied while persisting secret file.",
62 }),
63 ),
64 } satisfies FileSystem.FileSystem;
65 }),
66).pipe(Layer.provide(NodeServices.layer));
67
68const layerRenameFailureSecretStore = () =>
69 ServerSecretStore.layer.pipe(
70 Layer.provide(layerServerConfig()),
71 Layer.provideMerge(layerRenameFailureFileSystem),
72 );
73
74const layerRemoveFailureFileSystem = Layer.effect(
75 FileSystem.FileSystem,
76 Effect.gen(function* () {
77 const fileSystem = yield* FileSystem.FileSystem;
78
79 return {
80 ...fileSystem,
81 remove: (path, options) =>
82 Effect.fail(
83 PlatformError.systemError({
84 _tag: "PermissionDenied",
85 module: "FileSystem",
86 method: "remove",
87 pathOrDescriptor: String(path),
88 description: `Permission denied while removing secret file.${options ? " options-set" : ""}`,
89 }),
90 ),
91 } satisfies FileSystem.FileSystem;
92 }),
93).pipe(Layer.provide(NodeServices.layer));
94
95const layerRemoveFailureSecretStore = () =>
96 ServerSecretStore.layer.pipe(
97 Layer.provide(layerServerConfig()),
98 Layer.provideMerge(layerRemoveFailureFileSystem),
99 );
100
101const layerConcurrentReadMissFileSystem = Layer.effect(
102 FileSystem.FileSystem,
103 Effect.gen(function* () {
104 const fileSystem = yield* FileSystem.FileSystem;
105 const readCountRef = yield* Ref.make(0);
106 const readBarrier = yield* Deferred.make<void>();
107
108 return {
109 ...fileSystem,
110 readFile: (path) =>
111 /[\\/]session-signing-key\.bin$/.test(String(path))
112 ? Ref.updateAndGet(readCountRef, (count) => count + 1).pipe(
113 Effect.flatMap((count) => {
114 if (count > 2) {
115 return fileSystem.readFile(path);
116 }
117 return Effect.gen(function* () {
118 if (count === 2) {
119 yield* Deferred.succeed(readBarrier, void 0);
120 }
121 yield* Deferred.await(readBarrier);
122 return yield* Effect.failCause(
123 Cause.fail(
124 PlatformError.systemError({
125 _tag: "NotFound",
126 module: "FileSystem",
127 method: "readFile",
128 pathOrDescriptor: String(path),
129 description: "Secret file does not exist yet.",
130 }),
131 ),
132 );
133 });
134 }),
135 )
136 : fileSystem.readFile(path),
137 } satisfies FileSystem.FileSystem;
138 }),
139).pipe(Layer.provide(NodeServices.layer));
140
141const layerConcurrentCreateSecretStore = () =>
142 ServerSecretStore.layer.pipe(
143 Layer.provide(layerServerConfig()),
144 Layer.provideMerge(layerConcurrentReadMissFileSystem),
145 );
146
147it.layer(NodeServices.layer)("ServerSecretStore.layer", (it) => {
148 it.effect("returns Option.none when a secret file does not exist", () =>
149 Effect.gen(function* () {
150 const secretStore = yield* ServerSecretStore.ServerSecretStore;
151
152 const secret = yield* secretStore.get("missing-secret");
153
154 assert.isTrue(Option.isNone(secret));
155 }).pipe(Effect.provide(layerServerSecretStore())),
156 );
157
158 it.effect("reuses an existing secret instead of regenerating it", () =>
159 Effect.gen(function* () {
160 const secretStore = yield* ServerSecretStore.ServerSecretStore;
161
162 const first = yield* secretStore.getOrCreateRandom("session-signing-key", 32);
163 const second = yield* secretStore.getOrCreateRandom("session-signing-key", 32);
164
165 assert.deepEqual(Array.from(second), Array.from(first));
166 }).pipe(Effect.provide(layerServerSecretStore())),
167 );
168
169 it.effect("returns the persisted secret when concurrent creators race", () =>
170 Effect.gen(function* () {
171 const secretStore = yield* ServerSecretStore.ServerSecretStore;
172
173 const [first, second] = yield* Effect.all(
174 [
175 secretStore.getOrCreateRandom("session-signing-key", 32),
176 secretStore.getOrCreateRandom("session-signing-key", 32),
177 ],
178 { concurrency: "unbounded" },
179 );
180 const persisted = yield* secretStore.get("session-signing-key");
181 const persistedBytes = Option.getOrThrow(persisted);
182
183 assert.deepEqual(Array.from(first), Array.from(persistedBytes));
184 assert.deepEqual(Array.from(second), Array.from(persistedBytes));
185 }).pipe(Effect.provide(layerConcurrentCreateSecretStore())),
186 );
187
188 it.effect("uses restrictive permissions for the secret directory and files", () =>
189 Effect.gen(function* () {
190 const chmodCalls: Array<{ readonly path: string; readonly mode: number }> = [];
191 const layerRecordingFileSystem = Layer.effect(
192 FileSystem.FileSystem,
193 Effect.gen(function* () {
194 const fileSystem = yield* FileSystem.FileSystem;
195
196 return {
197 ...fileSystem,
198 makeDirectory: () => Effect.void,
199 writeFile: () => Effect.void,
200 rename: () => Effect.void,
201 chmod: (path, mode) =>
202 Effect.sync(() => {
203 chmodCalls.push({ path: String(path), mode });
204 }),
205 } satisfies FileSystem.FileSystem;
206 }),
207 ).pipe(Layer.provide(NodeServices.layer));
208
209 const secretStore = yield* Effect.service(ServerSecretStore.ServerSecretStore).pipe(
210 Effect.provide(
211 ServerSecretStore.layer.pipe(
212 Layer.provide(layerServerConfig()),
213 Layer.provideMerge(layerRecordingFileSystem),
214 ),
215 ),
216 );
217
218 yield* secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3]));
219
220 assert.isTrue(
221 chmodCalls.some((call) => call.mode === 0o700 && /[\\/]secrets$/.test(call.path)),
222 );
223 assert.isAtLeast(chmodCalls.filter((call) => call.mode === 0o600).length, 2);
224 }).pipe(Effect.provide(NodeServices.layer)),
225 );
226
227 it.effect("propagates read failures other than missing-file errors", () =>
228 Effect.gen(function* () {
229 const secretStore = yield* ServerSecretStore.ServerSecretStore;
230
231 const error = yield* Effect.flip(secretStore.getOrCreateRandom("session-signing-key", 32));
232
233 assert.instanceOf(error, ServerSecretStore.SecretStoreReadError);
234 assert.include(error.message, "Failed to read secret session-signing-key.");
235 assert.instanceOf(error.cause, PlatformError.PlatformError);
236 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
237 }).pipe(Effect.provide(layerPermissionDeniedSecretStore())),
238 );
239
240 it.effect("propagates write failures instead of treating them as success", () =>
241 Effect.gen(function* () {
242 const secretStore = yield* ServerSecretStore.ServerSecretStore;
243
244 const error = yield* Effect.flip(
245 secretStore.set("session-signing-key", Uint8Array.from([1, 2, 3])),
246 );
247
248 assert.instanceOf(error, ServerSecretStore.SecretStorePersistError);
249 assert.include(error.message, "Failed to persist secret session-signing-key.");
250 assert.instanceOf(error.cause, PlatformError.PlatformError);
251 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
252 }).pipe(Effect.provide(layerRenameFailureSecretStore())),
253 );
254
255 it.effect("propagates remove failures other than missing-file errors", () =>
256 Effect.gen(function* () {
257 const secretStore = yield* ServerSecretStore.ServerSecretStore;
258
259 const error = yield* Effect.flip(secretStore.remove("session-signing-key"));
260
261 assert.instanceOf(error, ServerSecretStore.SecretStoreRemoveError);
262 assert.include(error.message, "Failed to remove secret session-signing-key.");
263 assert.instanceOf(error.cause, PlatformError.PlatformError);
264 assert.equal((error.cause as PlatformError.PlatformError).reason._tag, "PermissionDenied");
265 }).pipe(Effect.provide(layerRemoveFailureSecretStore())),
266 );
267});