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();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
vi.stubGlobal("window", { setInterval: () => 1, clearInterval: () => undefined });
|
||||
const oldPrompt = { requestId: "request-1", message: "Paste callback", kind: "manual" } as const;
|
||||
|
||||
@@ -205,7 +205,18 @@ export class AuthController {
|
||||
const flow = provider.authType === "oauth"
|
||||
? await this.api.startOAuthLogin(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);
|
||||
if (flow.status === "running") this.startPolling(flow.flowId);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user