Harden persisted authentication recovery

Treat malformed authentication records as missing so browser sign-in can recover, preserve real file-read failures, and pin the Azure Identity persistence dependencies exactly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alex Yang (DevDiv)
2026-07-20 14:32:51 -07:00
parent 59fc5616ec
commit 463a876284
3 changed files with 35 additions and 5 deletions
+12 -3
View File
@@ -28,14 +28,23 @@ let legacyAuthCacheRemoved = false;
useIdentityPlugin(cachePersistencePlugin);
async function loadAuthenticationRecord() {
export async function loadAuthenticationRecord({
readFile = fs.readFile,
deserialize = deserializeAuthenticationRecord,
authRecordFile = AUTH_RECORD_FILE,
} = {}) {
let serialized;
try {
const serialized = await fs.readFile(AUTH_RECORD_FILE, "utf-8");
return deserializeAuthenticationRecord(serialized);
serialized = await readFile(authRecordFile, "utf-8");
} catch (error) {
if (error?.code === "ENOENT") return undefined;
throw error;
}
try {
return deserialize(serialized);
} catch {
return undefined;
}
}
async function saveAuthenticationRecord(record) {
@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
import {
ConnectorAuthenticationRequiredError,
InteractiveAuthBroker,
loadAuthenticationRecord,
TOKEN_CACHE_NAME,
} from "./auth.mjs";
@@ -36,6 +37,7 @@ test("ARM token requests require an explicit browser sign-in", async () => {
},
loadAuthRecord: async () => undefined,
});
await assert.rejects(
broker.getToken(),
(error) => error instanceof ConnectorAuthenticationRequiredError
@@ -47,6 +49,25 @@ test("ARM token requests require an explicit browser sign-in", async () => {
});
});
test("a malformed authentication record falls back to browser sign-in", async () => {
assert.equal(
await loadAuthenticationRecord({
readFile: async () => "{malformed-json",
}),
undefined,
);
});
test("authentication record read failures remain operational errors", async () => {
const readError = Object.assign(new Error("authentication record is unreadable"), { code: "EACCES" });
await assert.rejects(
loadAuthenticationRecord({
readFile: async () => { throw readError; },
}),
(error) => error === readError,
);
});
test("interactive sign-in reports pending then done and caches the ARM token", async () => {
let credentialOptions;
let authenticateOptions;
+2 -2
View File
@@ -14,8 +14,8 @@
],
"license": "MIT",
"dependencies": {
"@azure/identity": "^4.13.1",
"@azure/identity-cache-persistence": "^1.3.0",
"@azure/identity": "4.13.1",
"@azure/identity-cache-persistence": "1.3.0",
"@github/copilot-sdk": "1.0.6"
}
}