mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-14 05:06:54 +00:00
Fix concurrent token initialization
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -231,31 +231,35 @@ export class InteractiveAuthBroker {
|
|||||||
if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token;
|
if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token;
|
||||||
if (this.tokenInFlight) return this.tokenInFlight;
|
if (this.tokenInFlight) return this.tokenInFlight;
|
||||||
|
|
||||||
let credential = this.credential;
|
const request = (async () => {
|
||||||
if (!credential) {
|
let credential = this.credential;
|
||||||
try {
|
if (!credential) {
|
||||||
const authenticationRecord = await this.loadAuthRecord();
|
try {
|
||||||
credential = this.createInteractiveCredential(authenticationRecord);
|
const authenticationRecord = await this.loadAuthRecord();
|
||||||
this.credential = credential;
|
if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token;
|
||||||
} catch (error) {
|
credential = this.credential;
|
||||||
if (isAuthenticationRequiredError(error)) {
|
if (!credential) {
|
||||||
throw new ConnectorAuthenticationRequiredError(
|
credential = this.createInteractiveCredential(authenticationRecord);
|
||||||
"Sign in to Azure to continue.",
|
this.credential = credential;
|
||||||
{ cause: error },
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
if (isAuthenticationRequiredError(error)) {
|
||||||
|
throw new ConnectorAuthenticationRequiredError(
|
||||||
|
"Sign in to Azure to continue.",
|
||||||
|
{ cause: error },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
const request = credential.getToken(this.scope)
|
const accessToken = await credential.getToken(this.scope);
|
||||||
.then((accessToken) => {
|
|
||||||
if (!accessToken?.token || !Number.isFinite(accessToken.expiresOnTimestamp)) {
|
if (!accessToken?.token || !Number.isFinite(accessToken.expiresOnTimestamp)) {
|
||||||
throw new Error("Azure identity returned an incomplete ARM access token.");
|
throw new Error("Azure identity returned an incomplete ARM access token.");
|
||||||
}
|
}
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
return accessToken.token;
|
return accessToken.token;
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
|
||||||
if (!isAuthenticationRequiredError(error)) throw error;
|
if (!isAuthenticationRequiredError(error)) throw error;
|
||||||
if (this.credential === credential) {
|
if (this.credential === credential) {
|
||||||
this.credential = null;
|
this.credential = null;
|
||||||
@@ -265,12 +269,14 @@ export class InteractiveAuthBroker {
|
|||||||
"Sign in to Azure to continue.",
|
"Sign in to Azure to continue.",
|
||||||
{ cause: error },
|
{ cause: error },
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.finally(() => {
|
})();
|
||||||
if (this.tokenInFlight === request) this.tokenInFlight = null;
|
|
||||||
});
|
|
||||||
this.tokenInFlight = request;
|
this.tokenInFlight = request;
|
||||||
return request;
|
try {
|
||||||
|
return await request;
|
||||||
|
} finally {
|
||||||
|
if (this.tokenInFlight === request) this.tokenInFlight = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,47 @@ test("a new broker restores the persisted credential without reopening the brows
|
|||||||
assert.equal(authenticateCalls, 1);
|
assert.equal(authenticateCalls, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("concurrent first-time token requests share credential initialization and acquisition", async () => {
|
||||||
|
let releaseAuthenticationRecord;
|
||||||
|
const authenticationRecordReady = new Promise((resolve) => {
|
||||||
|
releaseAuthenticationRecord = resolve;
|
||||||
|
});
|
||||||
|
let loadAuthRecordCalls = 0;
|
||||||
|
let createCredentialCalls = 0;
|
||||||
|
let tokenCalls = 0;
|
||||||
|
const broker = new InteractiveAuthBroker({
|
||||||
|
async loadAuthRecord() {
|
||||||
|
loadAuthRecordCalls++;
|
||||||
|
await authenticationRecordReady;
|
||||||
|
return authenticationRecord();
|
||||||
|
},
|
||||||
|
createCredential: () => {
|
||||||
|
createCredentialCalls++;
|
||||||
|
return {
|
||||||
|
async getToken() {
|
||||||
|
tokenCalls++;
|
||||||
|
return accessToken("shared-token");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstRequest = broker.getToken();
|
||||||
|
const secondRequest = broker.getToken();
|
||||||
|
await new Promise((resolve) => setImmediate(resolve));
|
||||||
|
const loadsBeforeRelease = loadAuthRecordCalls;
|
||||||
|
releaseAuthenticationRecord();
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
await Promise.all([firstRequest, secondRequest]),
|
||||||
|
["shared-token", "shared-token"],
|
||||||
|
);
|
||||||
|
assert.equal(loadsBeforeRelease, 1);
|
||||||
|
assert.equal(loadAuthRecordCalls, 1);
|
||||||
|
assert.equal(createCredentialCalls, 1);
|
||||||
|
assert.equal(tokenCalls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
test("cancelling sign-in aborts the credential request", async () => {
|
test("cancelling sign-in aborts the credential request", async () => {
|
||||||
let abortSignal;
|
let abortSignal;
|
||||||
const credential = {
|
const credential = {
|
||||||
|
|||||||
Reference in New Issue
Block a user