import * as Context from "effect/Context";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 * as Schema from "effect/Schema"; import * as ServerConfig from "../config.ts"; const storedSecretErrorContext = { secretName: Schema.String, secretPath: Schema.String, cause: Schema.Defect(),}; export class SecretStoreDirectoryCreateError extends Schema.TaggedErrorClass<SecretStoreDirectoryCreateError>()( "SecretStoreDirectoryCreateError", { directoryPath: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to create secret store directory ${this.directoryPath}.`; }} export class SecretStoreDirectorySecureError extends Schema.TaggedErrorClass<SecretStoreDirectorySecureError>()( "SecretStoreDirectorySecureError", { directoryPath: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to secure secret store directory ${this.directoryPath}.`; }} export class SecretStoreReadError extends Schema.TaggedErrorClass<SecretStoreReadError>()( "SecretStoreReadError", { ...storedSecretErrorContext, },) { override get message(): string { return `Failed to read secret ${this.secretName} at ${this.secretPath}.`; }} export class SecretStoreTemporaryPathGenerationError extends Schema.TaggedErrorClass<SecretStoreTemporaryPathGenerationError>()( "SecretStoreTemporaryPathGenerationError", { secretName: Schema.String, secretPath: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to generate a temporary path for secret ${this.secretName} at ${this.secretPath}.`; }} export class SecretStorePersistError extends Schema.TaggedErrorClass<SecretStorePersistError>()( "SecretStorePersistError", { operation: Schema.Literals(["create", "set"]), ...storedSecretErrorContext, },) { override get message(): string { return `Failed to ${this.operation} secret ${this.secretName} at ${this.secretPath}.`; }} export class SecretStoreRandomGenerationError extends Schema.TaggedErrorClass<SecretStoreRandomGenerationError>()( "SecretStoreRandomGenerationError", { secretName: Schema.String, byteCount: Schema.Number, cause: Schema.Defect(), },) { override get message(): string { return `Failed to generate ${this.byteCount} random bytes for secret ${this.secretName}.`; }} export class SecretStoreConcurrentReadError extends Schema.TaggedErrorClass<SecretStoreConcurrentReadError>()( "SecretStoreConcurrentReadError", { secretName: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to read secret ${this.secretName} after concurrent creation.`; }} export class SecretStoreRemoveError extends Schema.TaggedErrorClass<SecretStoreRemoveError>()( "SecretStoreRemoveError", { ...storedSecretErrorContext, },) { override get message(): string { return `Failed to remove secret ${this.secretName} at ${this.secretPath}.`; }} export class SecretStoreDecodeError extends Schema.TaggedErrorClass<SecretStoreDecodeError>()( "SecretStoreDecodeError", { secretName: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to decode secret ${this.secretName}.`; }} export class SecretStoreEncodeError extends Schema.TaggedErrorClass<SecretStoreEncodeError>()( "SecretStoreEncodeError", { secretName: Schema.String, cause: Schema.Defect(), },) { override get message(): string { return `Failed to encode secret ${this.secretName}.`; }} export const SecretStoreError = Schema.Union([ SecretStoreDirectoryCreateError, SecretStoreDirectorySecureError, SecretStoreReadError, SecretStoreTemporaryPathGenerationError, SecretStorePersistError, SecretStoreRandomGenerationError, SecretStoreConcurrentReadError, SecretStoreRemoveError, SecretStoreDecodeError, SecretStoreEncodeError,]);export type SecretStoreError = typeof SecretStoreError.Type;export const isSecretStoreError = Schema.is(SecretStoreError); const isPlatformError = (value: unknown): value is PlatformError.PlatformError => Predicate.isTagged(value, "PlatformError"); export const isSecretAlreadyExistsError = (error: SecretStoreError): boolean => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists"; export class ServerSecretStore extends Context.Service< ServerSecretStore, { readonly get: (name: string) => Effect.Effect<Option.Option<Uint8Array>, SecretStoreReadError>; readonly set: ( name: string, value: Uint8Array, ) => Effect.Effect<void, SecretStoreTemporaryPathGenerationError | SecretStorePersistError>; readonly create: ( name: string, value: Uint8Array, ) => Effect.Effect<void, SecretStorePersistError>; readonly getOrCreateRandom: ( name: string, bytes: number, ) => Effect.Effect< Uint8Array, | SecretStoreReadError | SecretStoreRandomGenerationError | SecretStorePersistError | SecretStoreConcurrentReadError >; readonly remove: (name: string) => Effect.Effect<void, SecretStoreRemoveError>; }>()("t3/auth/ServerSecretStore") {} export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const serverConfig = yield* ServerConfig.ServerConfig; yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true }).pipe( Effect.mapError( (cause) => new SecretStoreDirectoryCreateError({ directoryPath: serverConfig.secretsDir, cause, }), ), ); yield* fileSystem.chmod(serverConfig.secretsDir, 0o700).pipe( Effect.mapError( (cause) => new SecretStoreDirectorySecureError({ directoryPath: serverConfig.secretsDir, cause, }), ), ); const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`); const get: ServerSecretStore["Service"]["get"] = (name) => { const secretPath = resolveSecretPath(name); return fileSystem.readFile(secretPath).pipe( Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail( new SecretStoreReadError({ secretName: name, secretPath, cause, }), ), ), Effect.withSpan("ServerSecretStore.get"), ); }; const set: ServerSecretStore["Service"]["set"] = (name, value) => { const secretPath = resolveSecretPath(name); return crypto.randomUUIDv4.pipe( Effect.mapError( (cause) => new SecretStoreTemporaryPathGenerationError({ secretName: name, secretPath, 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 SecretStorePersistError({ operation: "set", secretName: name, secretPath, cause, }), ), ), ), ), ); }), Effect.withSpan("ServerSecretStore.set"), ); }; const create: ServerSecretStore["Service"]["create"] = (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 SecretStorePersistError({ operation: "create", secretName: name, secretPath, cause, }), ), ); }; const getOrCreateRandom: ServerSecretStore["Service"]["getOrCreateRandom"] = (name, bytes) => get(name).pipe( Effect.flatMap( Option.match({ onSome: Effect.succeed, onNone: () => crypto.randomBytes(bytes).pipe( Effect.mapError( (cause) => new SecretStoreRandomGenerationError({ secretName: name, byteCount: bytes, cause, }), ), Effect.flatMap((generated) => create(name, generated).pipe( Effect.as(Uint8Array.from(generated)), Effect.catchTags({ SecretStorePersistError: (error) => isSecretAlreadyExistsError(error) ? get(name).pipe( Effect.flatMap( Option.match({ onSome: Effect.succeed, onNone: () => Effect.fail( new SecretStoreConcurrentReadError({ secretName: name, cause: error, }), ), }), ), ) : Effect.fail(error), }), ), ), ), }), ), Effect.withSpan("ServerSecretStore.getOrCreateRandom"), ); const remove: ServerSecretStore["Service"]["remove"] = (name) => { const secretPath = resolveSecretPath(name); return fileSystem.remove(secretPath).pipe( Effect.catch((cause) => cause.reason._tag === "NotFound" ? Effect.void : Effect.fail( new SecretStoreRemoveError({ secretName: name, secretPath, cause, }), ), ), Effect.withSpan("ServerSecretStore.remove"), ); }; return ServerSecretStore.of({ get, set, create, getOrCreateRandom, remove, });}); export const layer = Layer.effect(ServerSecretStore, make);
import * as Context from "effect/Context";
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 * as Schema from "effect/Schema";
import * as ServerConfig from "../config.ts";
const storedSecretErrorContext = {
secretName: Schema.String,
secretPath: Schema.String,
cause: Schema.Defect(),
};
export class SecretStoreDirectoryCreateError extends Schema.TaggedErrorClass<SecretStoreDirectoryCreateError>()(
"SecretStoreDirectoryCreateError",
{
directoryPath: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to create secret store directory ${this.directoryPath}.`;
}
}
export class SecretStoreDirectorySecureError extends Schema.TaggedErrorClass<SecretStoreDirectorySecureError>()(
"SecretStoreDirectorySecureError",
{
directoryPath: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to secure secret store directory ${this.directoryPath}.`;
}
}
export class SecretStoreReadError extends Schema.TaggedErrorClass<SecretStoreReadError>()(
"SecretStoreReadError",
{
...storedSecretErrorContext,
},
) {
override get message(): string {
return `Failed to read secret ${this.secretName} at ${this.secretPath}.`;
}
}
export class SecretStoreTemporaryPathGenerationError extends Schema.TaggedErrorClass<SecretStoreTemporaryPathGenerationError>()(
"SecretStoreTemporaryPathGenerationError",
{
secretName: Schema.String,
secretPath: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to generate a temporary path for secret ${this.secretName} at ${this.secretPath}.`;
}
}
export class SecretStorePersistError extends Schema.TaggedErrorClass<SecretStorePersistError>()(
"SecretStorePersistError",
{
operation: Schema.Literals(["create", "set"]),
...storedSecretErrorContext,
},
) {
override get message(): string {
return `Failed to ${this.operation} secret ${this.secretName} at ${this.secretPath}.`;
}
}
export class SecretStoreRandomGenerationError extends Schema.TaggedErrorClass<SecretStoreRandomGenerationError>()(
"SecretStoreRandomGenerationError",
{
secretName: Schema.String,
byteCount: Schema.Number,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to generate ${this.byteCount} random bytes for secret ${this.secretName}.`;
}
}
export class SecretStoreConcurrentReadError extends Schema.TaggedErrorClass<SecretStoreConcurrentReadError>()(
"SecretStoreConcurrentReadError",
{
secretName: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to read secret ${this.secretName} after concurrent creation.`;
}
}
export class SecretStoreRemoveError extends Schema.TaggedErrorClass<SecretStoreRemoveError>()(
"SecretStoreRemoveError",
{
...storedSecretErrorContext,
},
) {
override get message(): string {
return `Failed to remove secret ${this.secretName} at ${this.secretPath}.`;
}
}
export class SecretStoreDecodeError extends Schema.TaggedErrorClass<SecretStoreDecodeError>()(
"SecretStoreDecodeError",
{
secretName: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to decode secret ${this.secretName}.`;
}
}
export class SecretStoreEncodeError extends Schema.TaggedErrorClass<SecretStoreEncodeError>()(
"SecretStoreEncodeError",
{
secretName: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to encode secret ${this.secretName}.`;
}
}
export const SecretStoreError = Schema.Union([
SecretStoreDirectoryCreateError,
SecretStoreDirectorySecureError,
SecretStoreReadError,
SecretStoreTemporaryPathGenerationError,
SecretStorePersistError,
SecretStoreRandomGenerationError,
SecretStoreConcurrentReadError,
SecretStoreRemoveError,
SecretStoreDecodeError,
SecretStoreEncodeError,
]);
export type SecretStoreError = typeof SecretStoreError.Type;
export const isSecretStoreError = Schema.is(SecretStoreError);
const isPlatformError = (value: unknown): value is PlatformError.PlatformError =>
Predicate.isTagged(value, "PlatformError");
export const isSecretAlreadyExistsError = (error: SecretStoreError): boolean =>
"cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
export class ServerSecretStore extends Context.Service<
ServerSecretStore,
{
readonly get: (name: string) => Effect.Effect<Option.Option<Uint8Array>, SecretStoreReadError>;
readonly set: (
name: string,
value: Uint8Array,
) => Effect.Effect<void, SecretStoreTemporaryPathGenerationError | SecretStorePersistError>;
readonly create: (
name: string,
value: Uint8Array,
) => Effect.Effect<void, SecretStorePersistError>;
readonly getOrCreateRandom: (
name: string,
bytes: number,
) => Effect.Effect<
Uint8Array,
| SecretStoreReadError
| SecretStoreRandomGenerationError
| SecretStorePersistError
| SecretStoreConcurrentReadError
>;
readonly remove: (name: string) => Effect.Effect<void, SecretStoreRemoveError>;
}
>()("t3/auth/ServerSecretStore") {}
export const make = Effect.gen(function* () {
const crypto = yield* Crypto.Crypto;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const serverConfig = yield* ServerConfig.ServerConfig;
yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true }).pipe(
Effect.mapError(
(cause) =>
new SecretStoreDirectoryCreateError({
directoryPath: serverConfig.secretsDir,
cause,
}),
),
);
yield* fileSystem.chmod(serverConfig.secretsDir, 0o700).pipe(
Effect.mapError(
(cause) =>
new SecretStoreDirectorySecureError({
directoryPath: serverConfig.secretsDir,
cause,
}),
),
);
const resolveSecretPath = (name: string) => path.join(serverConfig.secretsDir, `${name}.bin`);
const get: ServerSecretStore["Service"]["get"] = (name) => {
const secretPath = resolveSecretPath(name);
return fileSystem.readFile(secretPath).pipe(
Effect.map((bytes) => Option.some(Uint8Array.from(bytes))),
Effect.catch((cause) =>
cause.reason._tag === "NotFound"
? Effect.succeed(Option.none())
: Effect.fail(
new SecretStoreReadError({
secretName: name,
secretPath,
cause,
}),
),
),
Effect.withSpan("ServerSecretStore.get"),
);
};
const set: ServerSecretStore["Service"]["set"] = (name, value) => {
const secretPath = resolveSecretPath(name);
return crypto.randomUUIDv4.pipe(
Effect.mapError(
(cause) =>
new SecretStoreTemporaryPathGenerationError({
secretName: name,
secretPath,
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 SecretStorePersistError({
operation: "set",
secretName: name,
secretPath,
cause,
}),
),
),
),
),
);
}),
Effect.withSpan("ServerSecretStore.set"),
);
};
const create: ServerSecretStore["Service"]["create"] = (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 SecretStorePersistError({
operation: "create",
secretName: name,
secretPath,
cause,
}),
),
);
};
const getOrCreateRandom: ServerSecretStore["Service"]["getOrCreateRandom"] = (name, bytes) =>
get(name).pipe(
Effect.flatMap(
Option.match({
onSome: Effect.succeed,
onNone: () =>
crypto.randomBytes(bytes).pipe(
Effect.mapError(
(cause) =>
new SecretStoreRandomGenerationError({
secretName: name,
byteCount: bytes,
cause,
}),
),
Effect.flatMap((generated) =>
create(name, generated).pipe(
Effect.as(Uint8Array.from(generated)),
Effect.catchTags({
SecretStorePersistError: (error) =>
isSecretAlreadyExistsError(error)
? get(name).pipe(
Effect.flatMap(
Option.match({
onSome: Effect.succeed,
onNone: () =>
Effect.fail(
new SecretStoreConcurrentReadError({
secretName: name,
cause: error,
}),
),
}),
),
)
: Effect.fail(error),
}),
),
),
),
}),
),
Effect.withSpan("ServerSecretStore.getOrCreateRandom"),
);
const remove: ServerSecretStore["Service"]["remove"] = (name) => {
const secretPath = resolveSecretPath(name);
return fileSystem.remove(secretPath).pipe(
Effect.catch((cause) =>
cause.reason._tag === "NotFound"
? Effect.void
: Effect.fail(
new SecretStoreRemoveError({
secretName: name,
secretPath,
cause,
}),
),
),
Effect.withSpan("ServerSecretStore.remove"),
);
};
return ServerSecretStore.of({
get,
set,
create,
getOrCreateRandom,
remove,
});
});
export const layer = Layer.effect(ServerSecretStore, make);