Files
openidea/src/features/ssh/askpass.ts
T
arookieofcanda-rookie-of-C-language e85ffa2ae8 feat(git): remember HTTPS credentials in the app secret store (#321)
Git no longer asks for the HTTPS user name and password on every fetch, pull and push. The password prompt offers "Remember for this host"; the credential is saved in the app's secret store (next to the saved SSH passwords, never in git's global configuration) only after the git command that used it succeeds, and a stored credential the server rejects is forgotten so the next command asks again. Settings › Version Control › Git lists saved credentials and forgets them.

Each git child gets its own id, passed to the askpass helper, so concurrent commands never save or forget each other's credentials. ide-vcs reports command completion through an observer installed at start-up instead of depending on ide-ssh. Credentials are keyed by scheme, host and port, and only git's authentication errors (Authentication failed, HTTP 401/403 responses, invalid username or password) forget a stored credential.

Fixes #272

Co-authored-by: a-rookie-of-C-language <[email protected]>
2026-10-30 10:48:29 +08:00

48 lines
1.6 KiB
TypeScript

/**
* Credential prompts of `ssh` / `git` child processes (backend askpass), shown
* one at a time; prompts arriving meanwhile wait in order.
*/
import { create } from "zustand";
import { commands, type AskpassKind } from "@/platform/ipc";
export interface AskpassPrompt {
id: number;
prompt: string;
kind: AskpassKind;
/** The dialog offers "Remember" (git HTTPS passwords). */
remember: boolean;
}
export const useAskpass = create<{ queue: AskpassPrompt[] }>(() => ({ queue: [] }));
export function enqueueAskpass(p: AskpassPrompt) {
useAskpass.setState((s) => (s.queue.some((q) => q.id === p.id) ? s : { queue: [...s.queue, p] }));
}
/** Drops prompt `id` (answered in another window, or the process gave up). */
export function dropAskpass(id: number) {
useAskpass.setState((s) => (s.queue.some((q) => q.id === id) ? { queue: s.queue.filter((q) => q.id !== id) } : s));
}
/** The answer sent back: the typed text, `yes` / `no` for a confirmation, null when cancelled. */
export function askpassAnswer(
kind: AskpassKind,
choice: { text: string } | { confirm: boolean } | null,
): string | null {
if (choice === null) return null;
if ("confirm" in choice) return kind === "confirm" ? (choice.confirm ? "yes" : "no") : null;
return kind === "confirm" ? null : choice.text;
}
export function answerAskpass(
p: AskpassPrompt,
answer: string | null,
remember: boolean,
send = commands.sshAskpassAnswer,
) {
dropAskpass(p.id);
// The backend ties `remember` to this prompt; the git command saves it only
// on success and drops it again when authentication fails.
void send(p.id, answer, remember).catch(() => {});
}