fix(auth): close CURRENT publication race

This commit is contained in:
User
2026-08-21 22:51:38 +02:00
parent da2f4a6681
commit 1e2c4e65c5
2 changed files with 74 additions and 9 deletions
+57 -1
View File
@@ -24,6 +24,9 @@ import { createCurrentLocalUserRegistryResolver } from "../src/auth/local-regist
const fsHook = vi.hoisted(() => ({
path: undefined as string | undefined,
callback: undefined as (() => void) | undefined,
fstatCallback: undefined as
| ((value: import("node:fs").Stats) => void)
| undefined,
rejectPathReaddir: false,
foreignGid: undefined as number | undefined,
}));
@@ -48,7 +51,9 @@ vi.mock("node:fs", async (importOriginal) => {
return result;
},
fstatSync(fd: number) {
return observed(actual.fstatSync(fd));
const result = observed(actual.fstatSync(fd));
fsHook.fstatCallback?.(result);
return result;
},
readdirSync(path: import("node:fs").PathLike) {
if (fsHook.rejectPathReaddir)
@@ -68,6 +73,7 @@ const roots: string[] = [];
afterEach(() => {
fsHook.path = undefined;
fsHook.callback = undefined;
fsHook.fstatCallback = undefined;
fsHook.rejectPathReaddir = false;
fsHook.foreignGid = undefined;
for (const root of roots.splice(0))
@@ -592,6 +598,56 @@ test("retries once when CURRENT is replaced after the final identity read", () =
expect(observations).toBe(3);
});
test("retries once when CURRENT is replaced between root descriptor and path observations", () => {
const root = projectionRoot();
const first = writeReadyProjection(
root,
localProjectionFixture("first", passwordHash),
);
const second = writeGeneration(
root,
localProjectionFixture("second", passwordHash),
);
const stagedParent = mkdtempSync(
join(tmpdir(), "tht-auth-projection-root-observation-"),
);
roots.push(stagedParent);
renameSync(join(root, "generations", second), join(stagedParent, second));
const rootIdentity = lstatSync(root);
let armed = false;
let replacedCurrent = false;
fsHook.path = join(root, "generations", first, "users.yaml");
fsHook.callback = () => {
armed = true;
fsHook.callback = undefined;
};
fsHook.fstatCallback = (value) => {
if (
!armed ||
replacedCurrent ||
value.dev !== rootIdentity.dev ||
value.ino !== rootIdentity.ino
)
return;
replacedCurrent = true;
renameSync(join(stagedParent, second), join(root, "generations", second));
const temporary = join(root, ".current-root-observation.tmp");
writeFileSync(temporary, currentDocument(second, [first]), {
encoding: "utf8",
mode: 0o600,
});
chmodSync(temporary, 0o600);
renameSync(temporary, join(root, "CURRENT"));
};
expect(
createProjectedAuthenticationConfigProvider(root).current()
.runtimeProjection?.generation,
).toBe(second);
expect(replacedCurrent).toBe(true);
});
test("rejects a second CURRENT replacement after the one permitted retry", () => {
const root = projectionRoot();
const first = writeReadyProjection(