Skip to content
Source
Sign inSign up

replayMarkers.ts

67 lines2.6 KB

1import * as Clock from "effect/Clock";
2import * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7import * as Path from "effect/Path";
8import * as Schedule from "effect/Schedule";
9​
10import { CLOUD_REPLAY_MARKER_PREFIXES } from "../cloud/CloudLink.ts";
11import * as ServerConfig from "../config.ts";
12import { forkParked } from "../serverActivation.ts";
13import { DPOP_REPLAY_MARKER_PREFIX } from "./dpop.ts";
14​
15const REPLAY_MARKER_PREFIXES = [DPOP_REPLAY_MARKER_PREFIX, ...CLOUD_REPLAY_MARKER_PREFIXES];
16​
17/**
18 * A marker only matters while its proof can pass the time check (about 10 minutes
19 * for DPoP, 7 for cloud proofs). After that, the time check rejects a replay by
20 * itself.
21 */
22export const REPLAY_MARKER_MAX_AGE = Duration.days(1);
23​
24/** Prunes only <marker prefix>*.bin, excluding other secrets and temporary files. */
25export const pruneExpiredReplayMarkers = Effect.fn("replayMarkers.pruneExpired")(function* () {
26 const fileSystem = yield* FileSystem.FileSystem;
27 const path = yield* Path.Path;
28 const { secretsDir } = yield* ServerConfig.ServerConfig;
29 const cutoff = (yield* Clock.currentTimeMillis) - Duration.toMillis(REPLAY_MARKER_MAX_AGE);
30 const markers = (yield* fileSystem.readDirectory(secretsDir)).filter(
31 (name) =>
32 name.endsWith(".bin") && REPLAY_MARKER_PREFIXES.some((prefix) => name.startsWith(prefix)),
33 );
34 const [removed, failures] = yield* Effect.partition(markers, (name) => {
35 const markerPath = path.join(secretsDir, name);
36 return fileSystem.stat(markerPath).pipe(
37 Effect.flatMap((info) =>
38 Option.exists(info.mtime, (mtime) => mtime.getTime() < cutoff)
39 ? fileSystem.remove(markerPath).pipe(Effect.as(true))
40 : Effect.succeed(false),
41 ),
42 Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(false)),
43 );
44 });
45 yield* Effect.annotateCurrentSpan({
46 "replay_markers.matched": markers.length,
47 "replay_markers.removed": removed.filter(Boolean).length,
48 "replay_markers.failed": failures.length,
49 });
50 if (failures.length > 0) {
51 yield* Effect.logWarning("Failed to prune some replay markers", {
52 failed: failures.length,
53 cause: failures[0],
54 });
55 }
56});
57​
58export const layer = Layer.effectDiscard(
59 forkParked(
60 pruneExpiredReplayMarkers().pipe(
61 Effect.catch((cause) =>
62 Effect.logWarning("Failed to prune expired replay markers", { cause }),
63 ),
64 Effect.repeat(Schedule.spaced(Duration.hours(1))),
65 ),
66 ),
67);