Archived
fix(auth): cancel stale started flows
Best-effort cancel a running auth flow when its start response arrives after the browser operation was closed or superseded, so sessiond does not retain orphaned provider polling or callback listeners.\n\nRefs #72
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@jmfederico/pi-web": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Cancel auth flows created after their browser start operation becomes stale, preventing abandoned provider polling or callback listeners after the dialog closes.
|
||||||
@@ -191,6 +191,34 @@ describe("AuthController", () => {
|
|||||||
expect(getState().authDialog).toBeUndefined();
|
expect(getState().authDialog).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("best-effort cancels a running flow whose start response arrives after the dialog closes", async () => {
|
||||||
|
const start = deferred<OAuthFlowState>();
|
||||||
|
const provider: AuthProviderOption = { ...authProvider("amazon-bedrock", "api_key"), loginFlow: "interactive" };
|
||||||
|
const cancelCalls: { flowId: string; machineId: string | undefined }[] = [];
|
||||||
|
const { controller, getState } = createController(
|
||||||
|
{
|
||||||
|
selectedMachine: remoteMachine("remote-1"),
|
||||||
|
authDialog: { step: "providers", mode: "login", authType: "api_key", providers: [provider] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startInteractiveApiKeyLogin: () => start.promise,
|
||||||
|
cancelOAuthFlow: (flowId, machineId) => {
|
||||||
|
cancelCalls.push({ flowId, machineId });
|
||||||
|
return Promise.reject(new Error("Cancel unavailable"));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingStart = controller.selectLoginProvider(provider.id, provider.authType);
|
||||||
|
controller.closeDialog();
|
||||||
|
start.resolve(oauthFlow({ flowId: "stale-flow", providerId: provider.id, providerName: provider.name }));
|
||||||
|
await pendingStart;
|
||||||
|
|
||||||
|
expect(cancelCalls).toEqual([{ flowId: "stale-flow", machineId: "remote-1" }]);
|
||||||
|
expect(getState().authDialog).toBeUndefined();
|
||||||
|
expect(getState().error).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not let a stale OAuth response overwrite a newer flow", async () => {
|
it("does not let a stale OAuth response overwrite a newer flow", async () => {
|
||||||
vi.stubGlobal("window", { setInterval: () => 1, clearInterval: () => undefined });
|
vi.stubGlobal("window", { setInterval: () => 1, clearInterval: () => undefined });
|
||||||
const oldPrompt = { requestId: "request-1", message: "Paste callback", kind: "manual" } as const;
|
const oldPrompt = { requestId: "request-1", message: "Paste callback", kind: "manual" } as const;
|
||||||
|
|||||||
@@ -205,7 +205,18 @@ export class AuthController {
|
|||||||
const flow = provider.authType === "oauth"
|
const flow = provider.authType === "oauth"
|
||||||
? await this.api.startOAuthLogin(provider.id, machineId)
|
? await this.api.startOAuthLogin(provider.id, machineId)
|
||||||
: await this.api.startInteractiveApiKeyLogin(provider.id, machineId);
|
: await this.api.startInteractiveApiKeyLogin(provider.id, machineId);
|
||||||
if (operationGeneration !== this.oauthOperationGeneration) return;
|
if (operationGeneration !== this.oauthOperationGeneration) {
|
||||||
|
// Sessiond has already allocated this flow. Do not orphan its timer,
|
||||||
|
// provider polling, or callback listener when the UI operation is stale.
|
||||||
|
if (flow.status === "running") {
|
||||||
|
try {
|
||||||
|
await this.api.cancelOAuthFlow(flow.flowId, machineId);
|
||||||
|
} catch {
|
||||||
|
// Best-effort cleanup; the obsolete flow must not restore UI state.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.updateOAuthFlow(flow);
|
this.updateOAuthFlow(flow);
|
||||||
if (flow.status === "running") this.startPolling(flow.flowId);
|
if (flow.status === "running") this.startPolling(flow.flowId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user