Add persistent browser auth to connector canvas

Replace Azure CLI-only authentication with InteractiveBrowserCredential, protected sign-in lifecycle endpoints, and subscription refresh after sign-in. Persist the Azure Identity cache securely across extension reloads and align connector restart guidance with the GitHub Copilot app.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alex Yang (DevDiv)
2026-07-20 13:04:18 -07:00
parent 9ae2bdc8b7
commit 2f258c7226
16 changed files with 945 additions and 258 deletions
@@ -7,7 +7,7 @@
// loaders without a visible fallback. Reduced motion now stops the
// animation while forcing each loader into a visible static busy state;
// nearby text continues to communicate progress.
// 2. The "Restart your Copilot session" banner ignoring Dismiss. The real
// 2. The "Restart the GitHub Copilot app" banner ignoring Dismiss. The real
// root cause was CSS specificity: `.restart-banner{display:flex}` is an
// author rule with the same (0,1,0) specificity as the UA
// `[hidden]{display:none}` rule, so it overrode the hidden attribute and
@@ -56,6 +56,33 @@ test("setup subscription label names its select", () => {
assert.match(html, /<label for="sub-select">Subscription<\/label>/);
});
test("setup prompts, polls, cancels, and reloads subscriptions after browser sign-in", () => {
const html = renderSetupHtml([], "", "token");
assert.match(html, /id="signin-btn"/);
assert.match(html, /fetch\("\/api\/signin"/);
assert.match(html, /\/api\/signin\/status\?sessionId=/);
assert.match(html, /fetch\("\/api\/signin\/cancel"/);
assert.match(html, /setTimeout\(pollSignin, 2500\)/);
assert.match(html, /await loadSubscriptions\(true\)/);
assert.match(html, /\/api\/subscriptions/);
});
test("setup sign-in uses a compact borderless blank state", () => {
const html = renderSetupHtml([], "", "token");
assert.match(html, /id="signin-btn" class="item-add primary signin-primary"/);
assert.match(html, /id="cancel-signin-btn" class="signin-cancel"/);
assert.match(html, /Sign in to Azure to load your subscriptions and connector namespaces\./);
assert.doesNotMatch(html, /signin-row|signin-icon|signin-title|>Azure account</);
assert.doesNotMatch(html, /\.signin-panel\s*\{[^}]*(?:border|background|padding)\s*:/);
});
test("setup browser script parses after rendering", () => {
const html = renderSetupHtml([], "", "token");
const script = html.match(/<script>([\s\S]*)<\/script>/)?.[1];
assert.ok(script, "setup page must include its client script");
assert.doesNotThrow(() => new Function(script));
});
test("load-all and installed-state failures stay visible and fail closed", () => {
const setup = renderSetupHtml([], "", "token");
const catalog = catalogHtml();
@@ -143,6 +170,12 @@ test("restart banner dismiss is sticky against a racing state refresh", () => {
);
});
test("restart guidance names the GitHub Copilot app rather than the session", () => {
const html = catalogHtml();
assert.match(html, /Restart the GitHub Copilot app to use newly added tools\./);
assert.doesNotMatch(html, /Restart (?:your Copilot )?session/);
});
test("a global [hidden] reset makes the hidden attribute authoritative", () => {
// The actual dismiss bug: .restart-banner{display:flex} (an author rule)
// ties the UA [hidden]{display:none} rule on specificity and wins, so the