import * as Crypto from "effect/Crypto";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 { ServerConfig } from "../../config.ts";import { SecretStoreError, ServerSecretStore, type ServerSecretStoreShape,} from "../Services/ServerSecretStore.ts"; export const makeServerSecretStore = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const serverConfig = yield* ServerConfig; yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true }); yield* fileSystem.chmod(serverConfig.secretsDir, 0o700).pipe( Effect.mapError( (cause) => new SecretStoreError({ message: `Failed to secure secrets directory ${serverConfig.secretsDir}.`, cause, }), ), ); const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); const isPlatformError = (u: unknown): u is PlatformError.PlatformError => Predicate.isTagged(u, "PlatformError"); const get: ServerSecretStoreShape["get"] = (name) => fileSystem.readFile(resolveSecretPath(name)).pipe( Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail( new SecretStoreError({ message: `Failed to read secret ${name}.`, cause, }), ), ), ); const set: ServerSecretStoreShape["set"] = (name, value) => { const secretPath = resolveSecretPath(name); return crypto.randomUUIDv4.pipe( Effect.mapError( (cause) => new SecretStoreError({ message: `Failed to generate temporary secret path for ${name}.`, cause, }), ), Effect.flatMap((uuid) => { const tempPath = `${secretPath}.${uuid}.tmp`; return Effect.gen(function* () { yield* fileSystem.writeFile(tempPath, value); yield* fileSystem.chmod(tempPath, 0o600); yield* fileSystem.rename(tempPath, secretPath); yield* fileSystem.chmod(secretPath, 0o600); }).pipe( Effect.catch((cause) => fileSystem.remove(tempPath).pipe( Effect.ignore, Effect.flatMap(() => Effect.fail( new SecretStoreError({ message: `Failed to persist secret ${name}.`, cause, }), ), ), ), ), ); }), ); }; const create: ServerSecretStoreShape["set"] = (name, value) => { const secretPath = resolveSecretPath(name); return Effect.scoped( Effect.gen(function* () { const file = yield* fileSystem.open(secretPath, { flag: "wx", mode: 0o600, }); yield* file.writeAll(value); yield* file.sync; yield* fileSystem.chmod(secretPath, 0o600); }), ).pipe( Effect.mapError( (cause) => new SecretStoreError({ message: `Failed to persist secret ${name}.`, cause, }), ), ); }; const getOrCreateRandom: ServerSecretStoreShape["getOrCreateRandom"] = (name, bytes) => get(name).pipe( Effect.flatMap((existing) => { if (Option.isSome(existing)) { return Effect.succeed(existing.value); } return crypto.randomBytes(bytes).pipe( Effect.mapError( (cause) => new SecretStoreError({ message: `Failed to generate secret ${name}.`, cause, }), ), Effect.flatMap((generated) => create(name, generated).pipe( Effect.as(Uint8Array.from(generated)), Effect.catchTag("SecretStoreError", (error) => isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists" ? get(name).pipe( Effect.flatMap((created) => Option.match(created, { onSome: Effect.succeed, onNone: () => Effect.fail( new SecretStoreError({ message: `Failed to read secret ${name} after concurrent creation.`, }), ), }), ), ) : Effect.fail(error), ), ), ), ); }), ); const remove: ServerSecretStoreShape["remove"] = (name) => fileSystem.remove(resolveSecretPath(name)).pipe( Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.void : Effect.fail( new SecretStoreError({ message: `Failed to remove secret ${name}.`, cause, }), ), ), ); return { get, set, getOrCreateRandom, remove, } satisfies ServerSecretStoreShape;}); export const ServerSecretStoreLive = Layer.effect(ServerSecretStore, makeServerSecretStore);
import * as Crypto from "effect/Crypto";
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 { ServerConfig } from "../../config.ts";
import {
SecretStoreError,
ServerSecretStore,
type ServerSecretStoreShape,
} from "../Services/ServerSecretStore.ts";
export const makeServerSecretStore = Effect.gen(function* () {
const crypto = yield* Crypto.Crypto;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const serverConfig = yield* ServerConfig;
yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
yield* fileSystem.chmod(serverConfig.secretsDir, 0o700).pipe(
Effect.mapError(
(cause) =>
new SecretStoreError({
message: `Failed to secure secrets directory ${serverConfig.secretsDir}.`,
cause,
}),
),
);
const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`);
const isPlatformError = (u: unknown): u is PlatformError.PlatformError =>
Predicate.isTagged(u, "PlatformError");
const get: ServerSecretStoreShape["get"] = (name) =>
fileSystem.readFile(resolveSecretPath(name)).pipe(
Effect.map((bytes) => Option.some(Uint8Array.from(bytes))),
Effect.catch((cause) =>
cause.reason._tag === "NotFound"
? Effect.succeed(Option.none())
: Effect.fail(
new SecretStoreError({
message: `Failed to read secret ${name}.`,
cause,
}),
),
),
);
const set: ServerSecretStoreShape["set"] = (name, value) => {
const secretPath = resolveSecretPath(name);
return crypto.randomUUIDv4.pipe(
Effect.mapError(
(cause) =>
new SecretStoreError({
message: `Failed to generate temporary secret path for ${name}.`,
cause,
}),
),
Effect.flatMap((uuid) => {
const tempPath = `${secretPath}.${uuid}.tmp`;
return Effect.gen(function* () {
yield* fileSystem.writeFile(tempPath, value);
yield* fileSystem.chmod(tempPath, 0o600);
yield* fileSystem.rename(tempPath, secretPath);
yield* fileSystem.chmod(secretPath, 0o600);
}).pipe(
Effect.catch((cause) =>
fileSystem.remove(tempPath).pipe(
Effect.ignore,
Effect.flatMap(() =>
Effect.fail(
new SecretStoreError({
message: `Failed to persist secret ${name}.`,
cause,
}),
),
),
),
),
);
}),
);
};
const create: ServerSecretStoreShape["set"] = (name, value) => {
const secretPath = resolveSecretPath(name);
return Effect.scoped(
Effect.gen(function* () {
const file = yield* fileSystem.open(secretPath, {
flag: "wx",
mode: 0o600,
});
yield* file.writeAll(value);
yield* file.sync;
yield* fileSystem.chmod(secretPath, 0o600);
}),
).pipe(
Effect.mapError(
(cause) =>
new SecretStoreError({
message: `Failed to persist secret ${name}.`,
cause,
}),
),
);
};
const getOrCreateRandom: ServerSecretStoreShape["getOrCreateRandom"] = (name, bytes) =>
get(name).pipe(
Effect.flatMap((existing) => {
if (Option.isSome(existing)) {
return Effect.succeed(existing.value);
}
return crypto.randomBytes(bytes).pipe(
Effect.mapError(
(cause) =>
new SecretStoreError({
message: `Failed to generate secret ${name}.`,
cause,
}),
),
Effect.flatMap((generated) =>
create(name, generated).pipe(
Effect.as(Uint8Array.from(generated)),
Effect.catchTag("SecretStoreError", (error) =>
isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists"
? get(name).pipe(
Effect.flatMap((created) =>
Option.match(created, {
onSome: Effect.succeed,
onNone: () =>
Effect.fail(
new SecretStoreError({
message: `Failed to read secret ${name} after concurrent creation.`,
}),
),
}),
),
)
: Effect.fail(error),
),
),
),
);
}),
);
const remove: ServerSecretStoreShape["remove"] = (name) =>
fileSystem.remove(resolveSecretPath(name)).pipe(
Effect.catch((cause) =>
cause.reason._tag === "NotFound"
? Effect.void
: Effect.fail(
new SecretStoreError({
message: `Failed to remove secret ${name}.`,
cause,
}),
),
),
);
return {
get,
set,
getOrCreateRandom,
remove,
} satisfies ServerSecretStoreShape;
});
export const ServerSecretStoreLive = Layer.effect(ServerSecretStore, makeServerSecretStore);